1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2025-09-01 10:28:03 +00:00

Improved crypto and password prompting mechanism

Now encrypted tiddlywikis prompt for their password with an html form,
instead of a horrible javascript prompt.
This commit is contained in:
Jeremy Ruston
2012-11-16 16:59:47 +00:00
parent e76a7f1c01
commit db3a4651a2
4 changed files with 266 additions and 80 deletions

View File

@@ -39,43 +39,6 @@ if(!$tw.browser) {
require("./bootprefix.js"); require("./bootprefix.js");
} }
// Crypto helper object
// Setup crypto
var Crypto = function() {
var password = null,
callSjcl = function(method,inputText) {
var outputText;
if(!password) {
getPassword();
}
try {
outputText = $tw.crypto.sjcl[method](password,inputText);
} catch(ex) {
console.log("Crypto error:" + ex);
outputText = null;
}
return outputText;
},
getPassword = function() {
if($tw.browser) {
password = window.prompt("Enter password to decrypt TiddlyWiki");
}
};
this.setPassword = function(newPassword) {
password = newPassword;
};
this.encrypt = function(text) {
return callSjcl("encrypt",text);
};
this.decrypt = function(text) {
return callSjcl("decrypt",text);
};
};
$tw.crypto = new Crypto();
$tw.crypto.sjcl = $tw.browser ? window.sjcl : require("./sjcl.js");
// Boot information // Boot information
$tw.boot = {}; $tw.boot = {};
@@ -275,6 +238,106 @@ $tw.utils.checkVersions = function(required,actual) {
(diff[0] === 0 && diff[1] === 0 && diff[2] > 0); (diff[0] === 0 && diff[1] === 0 && diff[2] > 0);
}; };
/*
Creates a PasswordPrompt object
*/
$tw.utils.PasswordPrompt = function() {
// Store of pending password prompts
this.passwordPrompts = [];
// Create the wrapper
this.promptWrapper = document.createElement("div");
this.promptWrapper.className = "tw-password-wrapper";
document.body.appendChild(this.promptWrapper);
// Hide the empty wrapper
this.setWrapperDisplay();
};
/*
Hides or shows the wrapper depending on whether there are any outstanding prompts
*/
$tw.utils.PasswordPrompt.prototype.setWrapperDisplay = function() {
if(this.passwordPrompts.length) {
this.promptWrapper.style.display = "block";
} else {
this.promptWrapper.style.display = "none";
}
};
/*
Adds a new password prompt
*/
$tw.utils.PasswordPrompt.prototype.createPrompt = function(options) {
// Create and add the prompt to the DOM
var form = document.createElement("form"),
html = ["<h1>" + options.serviceName + "</h1>"];
if(!options.noUserName) {
html.push("<input type='text' name='username' class='input-small' placeholder='Username'>");
}
html.push("<input type='password' name='password' class='input-small' placeholder='Password'>",
"<button type='submit' class='btn'>Login</button>");
form.className = "form-inline";
form.innerHTML = html.join("\n");
this.promptWrapper.appendChild(form);
// Add a submit event handler
var self = this;
form.addEventListener("submit",function(event) {
// Collect the form data
var data = {};
for(var t=0; t<form.elements.length; t++) {
var e = form.elements[t];
if(e.name && e.value) {
data[e.name] = e.value;
}
}
// Call the callback
if(options.callback(data)) {
// Remove the prompt if the callback returned true
var i = self.passwordPrompts.indexOf(promptInfo);
if(i !== -1) {
self.passwordPrompts.splice(i,1);
promptInfo.form.parentNode.removeChild(promptInfo.form);
self.setWrapperDisplay();
}
}
event.preventDefault();
return false;
},true);
// Add the prompt to the list
var promptInfo = {
serviceName: options.serviceName,
callback: options.callback,
form: form
};
this.passwordPrompts.push(promptInfo);
// Make sure the wrapper is displayed
this.setWrapperDisplay();
};
// Crypto helper object for encrypted content
$tw.utils.Crypto = function() {
var sjcl = $tw.browser ? window.sjcl : require("./sjcl.js"),
password = null,
callSjcl = function(method,inputText) {
var outputText;
try {
outputText = sjcl[method](password,inputText);
} catch(ex) {
console.log("Crypto error:" + ex);
outputText = null;
}
return outputText;
};
this.setPassword = function(newPassword) {
password = newPassword;
};
this.encrypt = function(text) {
return callSjcl("encrypt",text);
};
this.decrypt = function(text) {
return callSjcl("decrypt",text);
};
};
/////////////////////////// Server initialisation /////////////////////////// Server initialisation
var fs, path, vm; var fs, path, vm;
@@ -587,6 +650,29 @@ $tw.wiki = new $tw.Wiki();
if($tw.browser) { if($tw.browser) {
/*
Get any encrypted tiddlers
*/
$tw.boot.getEncryptedTiddlers = function() {
var encryptedArea = document.getElementById("encryptedArea"),
encryptedTiddlers = [];
if(encryptedArea) {
for(var t = 0; t <encryptedArea.childNodes.length; t++) {
var childNode = encryptedArea.childNodes[t];
if(childNode.hasAttribute && childNode.hasAttribute("data-tw-encrypted-tiddlers")) {
var e = childNode.firstChild;
while(e && e.nodeName.toLowerCase() !== "pre") {
e = e.nextSibling;
}
encryptedTiddlers.push($tw.utils.htmlDecode(e.innerHTML));
}
}
return encryptedTiddlers;
} else {
return null;
}
};
/* /*
Execute the module named 'moduleName'. The name can optionally be relative to the module named 'moduleRoot' Execute the module named 'moduleName'. The name can optionally be relative to the module named 'moduleRoot'
*/ */
@@ -660,30 +746,12 @@ $tw.modules.define("$:/boot/tiddlerdeserializer/dom","tiddlerdeserializer",{
return null; return null;
} }
}, },
extractEncryptedTiddlers = function(node) {
if(node.hasAttribute && node.hasAttribute("data-tw-encrypted-tiddlers")) {
var e = node.firstChild;
while(e && e.nodeName.toLowerCase() !== "pre") {
e = e.nextSibling;
}
var jsonTiddlers = JSON.parse($tw.crypto.decrypt($tw.utils.htmlDecode(e.innerHTML))),
title,
result = [];
for(title in jsonTiddlers) {
result.push(jsonTiddlers[title]);
}
return result;
} else {
return null;
}
},
t,result = []; t,result = [];
if(node) { if(node) {
for(t = 0; t < node.childNodes.length; t++) { for(t = 0; t < node.childNodes.length; t++) {
var tiddlers = extractTextTiddlers(node.childNodes[t]), var tiddlers = extractTextTiddlers(node.childNodes[t]),
childNode = node.childNodes[t]; childNode = node.childNodes[t];
tiddlers = tiddlers || extractModuleTiddlers(childNode); tiddlers = tiddlers || extractModuleTiddlers(childNode);
tiddlers = tiddlers || extractEncryptedTiddlers(childNode);
if(tiddlers) { if(tiddlers) {
result.push.apply(result,tiddlers); result.push.apply(result,tiddlers);
} }
@@ -720,6 +788,14 @@ $tw.loadTiddlers = function() {
if(!$tw.browser) { if(!$tw.browser) {
/*
Get any encrypted tiddlers
*/
$tw.boot.getEncryptedTiddlers = function() {
// Storing encrypted tiddlers on the server isn't supported yet
return null;
};
/* /*
Load the tiddlers contained in a particular file (and optionally extract fields from the accompanying .meta file) Load the tiddlers contained in a particular file (and optionally extract fields from the accompanying .meta file)
*/ */
@@ -898,27 +974,79 @@ $tw.loadTiddlers = function() {
// End of if(!$tw.browser) // End of if(!$tw.browser)
} }
/////////////////////////// Final initialisation /////////////////////////// Starting up
/*
// Install built in tiddler fields modules Main startup function
$tw.Tiddler.fieldModules = $tw.modules.getModulesByTypeAsHashmap("tiddlerfield"); */
// Install the tiddler deserializer modules $tw.boot.startup = function() {
$tw.modules.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerModules); // Install built in tiddler fields modules
// Load tiddlers $tw.Tiddler.fieldModules = $tw.modules.getModulesByTypeAsHashmap("tiddlerfield");
$tw.loadTiddlers(); // Install the tiddler deserializer modules
// Unpack bundle tiddlers $tw.modules.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerModules);
$tw.wiki.unpackBundleTiddlers(); // Load tiddlers
// Register typed modules from the tiddlers we've just loaded $tw.loadTiddlers();
if(!$tw.browser) { // Unpack bundle tiddlers
$tw.wiki.defineTiddlerModules(); $tw.wiki.unpackBundleTiddlers();
} // Register typed modules from the tiddlers we've just loaded
$tw.wiki.defineBundledModules(); if(!$tw.browser) {
// Run any startup modules $tw.wiki.defineTiddlerModules();
$tw.modules.forEachModuleOfType("startup",function(title,module) {
if(module.startup) {
module.startup();
} }
}); $tw.wiki.defineBundledModules();
// Run any startup modules
$tw.modules.forEachModuleOfType("startup",function(title,module) {
if(module.startup) {
module.startup();
}
});
};
/*
Starting up
*/
// Initialise crypto object
$tw.crypto = new $tw.utils.Crypto();
// Initialise password prompter
if($tw.browser) {
$tw.passwordPrompt = new $tw.utils.PasswordPrompt();
}
// Get any encrypted tiddlers
var encryptedTiddlers = $tw.boot.getEncryptedTiddlers();
// Check if we need a password to decrypt tiddlers
if($tw.browser && encryptedTiddlers) {
// Prompt for the password
$tw.passwordPrompt.createPrompt({
serviceName: "Enter a password to decrypt this TiddlyWiki",
noUserName: true,
callback: function(data) {
// Attempt to decrypt the tiddlers
$tw.crypto.setPassword(data.password);
for(var t=encryptedTiddlers.length-1; t>=0; t--) {
var decrypted = $tw.crypto.decrypt(encryptedTiddlers[t]);
if(decrypted) {
var json = JSON.parse(decrypted);
for(var title in json) {
if($tw.utils.hop(json,title)) {
$tw.preloadTiddler(json[title]);
}
}
encryptedTiddlers.splice(t,1);
}
}
// Check if we're all done
if(encryptedTiddlers.length === 0) {
// Continue startup
$tw.boot.startup();
// Exit and remove the password prompt
return true;
} else {
// We didn't decrypt everything, so continue to prompt for password
return false;
}
}
});
} else {
$tw.boot.startup();
}
})(); })();

View File

@@ -5918,6 +5918,37 @@ body {
padding-top: 40px; padding-top: 40px;
} }
/*
Password prompts
*/
.tw-password-wrapper {
position: fixed;
top: 1em;
left: 50%;
z-index: 20000;
width: 480px;
padding: 16px 16px 16px 16px;
margin-left: -264px;
color: #000;
text-align: center;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
background-color: #c5ebb7;
border: 8px solid #a4c598;
-webkit-border-radius: 8px;
-moz-border-radius: 8px;
border-radius: 8px;
}
.tw-password-wrapper form {
text-align: left;
}
.tw-password-wrapper h1 {
font-size: 16px;
line-height: 20px;
}
/* /*
Tweaks for full screen mode Tweaks for full screen mode
*/ */

View File

@@ -31,12 +31,9 @@ type: text/x-tiddlywiki-html
<<serialize "$:/templates/StaticContent" text/html>> <<serialize "$:/templates/StaticContent" text/html>>
</div> </div>
</noscript> </noscript>
<!----------- Miscellaneous shadow tiddlers -----------> <!----------- Encrypted tiddlers ----------->
<div id="shadowArea" style="display:none;"> <div id="encryptedArea" style="display:none;">
<<serialize "[is[shadow]] -[type[text/css]] -[type[application/javascript]has[module-type]] -[type[application/javascript]library[yes]] -[[$:/core/boot.js]] -[[$:/core/bootprefix.js]]" application/x-tiddler-encrypted-div>> <<serialize "[is[shadow]] -[type[text/css]] -[type[application/javascript]has[module-type]] -[type[application/javascript]library[yes]] -[[$:/core/boot.js]] -[[$:/core/bootprefix.js]]" application/x-tiddler-encrypted-div>>
</div>
<!----------- Ordinary tiddlers ----------->
<div id="storeArea" style="display:none;">
<<serialize "$1" application/x-tiddler-encrypted-div>> <<serialize "$1" application/x-tiddler-encrypted-div>>
</div> </div>
<!----------- Library modules -----------> <!----------- Library modules ----------->

View File

@@ -42,6 +42,36 @@ body {
padding-top: @navbarHeight; // Allow for the navbar padding-top: @navbarHeight; // Allow for the navbar
} }
/*
Password prompts
*/
.tw-password-wrapper {
z-index: 20000;
position: fixed;
text-align: center;
width: 480px;
top: 1em;
left: 50%;
margin-left: -264px; /* - width/2 - paddingHorz/2 - border */
padding: 16px 16px 16px 16px;
-webkit-border-radius: 8px;
-moz-border-radius: 8px;
border-radius: 8px;
color: #000;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
background-color: rgb(197, 235, 183);
border: 8px solid rgb(164, 197, 152);
}
.tw-password-wrapper form {
text-align: left;
}
.tw-password-wrapper h1 {
font-size: 16px;
line-height: 20px;
}
/* /*
Tweaks for full screen mode Tweaks for full screen mode
*/ */