Save Your TiddlyWikis Without A Saver Or Plugin

Hello Everyone.
You can have that dream TiddlyWiki in THREE SIMPLE steps:

  1. Create a tiddler named: $:/config/SaveWikiButton/Filename
  2. For the text in that tiddler write *some variation of: <<now "YYYY0MM-0DD0hh-mm">> My TiddyWiki
    *Adjust to suit your needs.
  3. Save Your TiddlyWikis And Reopen The New Ones
    [CTRL+S] To Save
    [CTRL+J] To Open The Recently Downloaded List
    Enjoy!
10 Likes

Apologies…
The snippet didn’t seem to appear between the <>.

<<now YYYY0MM-0DD0hh0mm>> My TiddlyWiki 

Hope that helps you!

Very nice. I didn’t realize that you could do this. (Although I should have!)

And something else new to me. Very useful.

Thank you for sharing, and welcome to the talk.tiddlywiki.org community!

I suppose this implies a web browser and a desktop rather than a mobile context?

Great post, @AlfieA.

Just for fun, I asked ChatGPT what it thought of your method:

Are you heading over to the dark side?

:rofl:

What do you mean, heading? I never left :wink:

@EricShulman has recently shared a solution to have a custom save button. It can be used with your solution and create a magic save button.

If you use $:/config/SaveWikiButton/Filename with below content in the text, then you can only create maximum six backups, this will work similar to Timimi which reuses the first backup file when you created the maximum number of backups. Your folder will not be populated with many .html files.

{{$:/SiteTitle}}-backup{{{[<now mm>divide[10]floor[]]}}}

The minimum interval between two saves is 10 minutes (change it if you like), that means if you press save button in less than 10 minutes the file will be overwritten.

5 Likes

On Android, appears to work with Chrome and Kiwi browsers, at least if you are sending to the download directory. Works once with Firefox, but somewhat irrelevant since you can’t load a file back into Firefox.

Using the name “SaveNew.html” as the file name, I saved TiddlyWiki .com to a folder on my HD (my Firefox is set to “ask” where to save). Then I double clicked SaveNew.html and it opened fine in Firefox.

What was your method?

EDIT: Oh. Android. Nevermind.

Very kind of you, Scott. This is a great community.

It was fun reading you ChatGTP post, Coda. Thank you for doing that. Was very humbled.

1 Like

Thanks for the report, Mark.
I’d be interested to know how you use your phone for TiddlyWikiing.

1 Like

So, there’s a little problem with this approach.

On my test on two different chromium-based browsers, the minute part did not update. It appears to only update when the file is reloaded.

Does anyone know if there is a trick to force the $:/config/SaveWikiButton/Filename tiddler to update it’s internal value?

There are so many hassles on the phone, that I almost never use TW for note-taking there. I do use it for note-reading (like literature), and for checklist apps (travel lists). But use something else if I want to write a note, since browsers on phones/tablets will use any excuse to reload or clear your page, which leads to work loss. You can use it for notes, but you need to be hyper-vigilant about saving before leaving the page.

Mark, I have the same reloading issues with phones. At first I thought it may have been an old, cheap Android thing. But still happens with my new high-spec Android.
So… like you, I use the phone TiddlyWiki to take notes, but not to manage the TW. I export as .TID files immediately after drawing them up.

Hi everyone,

This is a really interesting thread about simple ways to save TiddlyWiki! I especially appreciate the tip from @AlfieA and the discussion about file naming and browser behaviors.

While the topic is focused on simple saving, the conversation got me thinking about security, and I wanted to introduce another layer of protection that users might find valuable, especially if their TiddlyWiki contains sensitive information.

We’ve been discussing ways to easily save and reopen TiddlyWiki files. But what about protecting access to that information itself? The proposed mechanism with time based filenames provides a good way to create backups of your TiddlyWiki, but it does not protect against unauthorized access to its content. That’s where the Time-Based One-Time Password (TOTP) and libsodium encryption come in.

The TOTP/Libsodium Idea:

The concept is to combine TOTP authentication with libsodium to encrypt and secure your TiddlyWiki from unauthorized access. Here’s a brief summary of how it could work:

  1. Initial Setup:
    • The user sets up TOTP using a key generated by a TOTP application or other TOTP provider.
    • The password you chose to protect your TiddlyWiki will be used as a key in order to decrypt the TOTP Key.
  2. Encryption: The TOTP secret key is encrypted using a key generated from the password.
  3. Saving the Encrypted Key: The encrypted key is stored within the TiddlyWiki file.
  4. Access:
    • When the user opens the TiddlyWiki, a login form will be displayed.
    • The user will be prompted for the password they chose during setup. This password will be used to decrypt the TOTP key.
    • The user must also provide the current TOTP code generated by an authenticator app.
    • If both the password and the code are valid, the TiddlyWiki is unlocked.

Why Use TOTP/Libsodium in This Context?

  • Increased Security: It adds an extra layer of security against unauthorized access to the TiddlyWiki content, even if someone gets their hands on the HTML file. The time-based backup files could be stored without much risk, as the content is protected by TOTP.
  • Flexibility: TOTP works on any device with a TOTP app, so it doesn’t necessarily lock you into a single environment. The combination of a password and TOTP allows for two-factor authentication.
  • Privacy: With the encryption, the content of your TiddlyWiki is protected.

A Conceptual Approach (Based on Previous Discussion)

I’ve been exploring a possible implementation in the form of a plugin. It’s not a production-ready solution, but here’s a simplified version of the core ideas using JavaScript in a TiddlyWiki plugin:

(function() {

  /*jshint esversion: 6 */

  exports.name = 'totp-auth';
  exports.version = '0.1.0';

  const sodium = require('libsodium-wrappers'); // or tweetnacl
  
  function deriveKey(password, salt) {
    const passwordBuffer = new TextEncoder().encode(password);
    const saltBuffer = new Uint8Array(salt);
    return sodium.crypto_pwhash(
        sodium.crypto_secretbox_KEYBYTES,
        passwordBuffer,
        saltBuffer,
        sodium.crypto_pwhash_OPSLIMIT_MODERATE,
        sodium.crypto_pwhash_MEMLIMIT_MODERATE,
        sodium.crypto_pwhash_ALG_DEFAULT
    );
  }

  exports.startup = function() {
    
    const storage = $tw.wiki.getTiddlerData("$:/plugins/YourPluginName/storage",{});
    
    
    // Function to generate the libsodium key pair and encrypted TOTP key
    $tw.wiki.generateEncryptedTotpKey = function (password, totpKey) {
        const salt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES);
        const derivedKey = deriveKey(password, salt);
        const keyPair = sodium.crypto_box_keypair();
        const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
        const totpKeyBuffer = new TextEncoder().encode(totpKey);
        const encryptedTotpKey = sodium.crypto_box(totpKeyBuffer, nonce, keyPair.publicKey, keyPair.privateKey);


        // Save all, the salt must be stored in plain text because it is used to decrypt the password
        storage.sodiumPublicKey = sodium.to_base64(keyPair.publicKey);
        storage.encryptedTotpKey = sodium.to_base64(encryptedTotpKey);
        storage.nonce = sodium.to_base64(nonce);
        storage.salt = sodium.to_base64(salt);

       $tw.wiki.setTiddlerData("$:/plugins/YourPluginName/storage", storage);

       return true;


    };


    // Function to decrypt the TOTP key and generate the code
    $tw.wiki.getTotpCode = function (password) {

        if(!storage.salt || !storage.encryptedTotpKey || !storage.nonce || !storage.sodiumPublicKey) {
          return undefined;
        }

        const salt = sodium.from_base64(storage.salt);
        const derivedKey = deriveKey(password, salt);
        const encryptedTotpKey = sodium.from_base64(storage.encryptedTotpKey);
        const nonce = sodium.from_base64(storage.nonce);
        const publicKey = sodium.from_base64(storage.sodiumPublicKey);
        const privateKey = sodium.crypto_box_keypair(publicKey, derivedKey);


        try {
            const decryptedTotpKeyBuffer = sodium.crypto_box_open(encryptedTotpKey, nonce, publicKey, privateKey);
            const decryptedTotpKey = new TextDecoder().decode(decryptedTotpKeyBuffer);
            const totp = require('jsotp');
            const totpInstance = totp.TOTP(decryptedTotpKey);
            return totpInstance.now();
        } catch (e) {
             //console.error(e)
            return undefined;
        }


    };

    $tw.wiki.isAuthenticated = function() {
        return $tw.wiki.getTiddlerText("$:/state/totp/authenticated") === "true";
    };

    $tw.wiki.updateAuthenticatedState = function (isAuthenticated) {
      $tw.wiki.setText("$:/state/totp/authenticated", undefined, isAuthenticated ? "true" : "false");
    }


  };


})();
<div id="login-form" style="display:none">
  <h2>Login TOTP</h2>
  <p>
     Mot de passe:
     <input type="password" id="password-input">
   </p>
   <p>
     Code TOTP:
     <input type="text" id="totp-input">
    </p>
  <p>
   <button id="login-button">Valider TOTP</button>
  </p>
  <p>
   <button id="generate-key-button">Générer clé TOTP</button>
  </p>
    <div id="error-message" style="color:red"></div>
</div>

<script>

const storage = $tw.wiki.getTiddlerData("$:/plugins/YourPluginName/storage",{});

if(!$tw.wiki.isAuthenticated())
{

  const loginDiv = document.getElementById('login-form');
  loginDiv.style.display = 'block';
  const passwordInput = document.getElementById('password-input');
  const totpInput = document.getElementById('totp-input');
  const errorMessage = document.getElementById('error-message');

  if (!storage.sodiumPublicKey){
      document.getElementById("login-button").style.display = 'none';

    document.getElementById('generate-key-button').addEventListener('click', function() {
      const password = passwordInput.value;
      const totpKey = prompt("Entrez votre clé TOTP (ex: 12345678901234567890)");
      if(totpKey && password){
          if ($tw.wiki.generateEncryptedTotpKey(password, totpKey)){
              errorMessage.textContent = "Clé TOTP sauvegardée. Vous pouvez vous authentifier.";
              document.getElementById("login-button").style.display = 'inline-block';
                document.getElementById("generate-key-button").style.display = 'none';
          } else {
              errorMessage.textContent = "Erreur lors de la sauvegarde de la clé TOTP.";
          }
      } else {
        errorMessage.textContent = "Veuillez fournir un mot de passe et une clé TOTP.";
      }
    });
  }else{
      document.getElementById("generate-key-button").style.display = 'none';
      document.getElementById('login-button').addEventListener('click', function() {
          const password = passwordInput.value;
        const totpCode = totpInput.value;

        if(!password || !totpCode){
          errorMessage.textContent = "Veuillez entrer un mot de passe et un code TOTP.";
           return;
        }

        const generatedCode = $tw.wiki.getTotpCode(password);
        if(generatedCode && generatedCode === totpCode){
           errorMessage.textContent = "Authentification réussie.";
            $tw.wiki.updateAuthenticatedState(true)
             loginDiv.style.display = 'none';
        } else {
           errorMessage.textContent = "Code TOTP invalide ou mot de passe incorrect.";
        }
      });
  }
}
</script>

Important Notes:

  • This is a conceptual idea: This is not a production-ready plugin. Further work, testing, and security review are needed.
  • Libraries: This code would need the libsodium-wrappers or tweetnacl and jsotp libraries to be loaded in TiddlyWiki

Discussion:

While the current focus is on simpler save methods, I thought this idea might be interesting for users looking for enhanced security.

I’m curious to hear your thoughts on this approach. Do you think this kind of security is worth exploring? Any suggestions or feedback are welcome!

1 Like

I have a purely end user question. How’s the resource demand of this approach compared to the current single file encryption that TiddlyWiki provides? I’m encrypting all my wikis that contain personal data, but as they grow, fully autosaving them after each tiddler change takes more time than I’d like, and eats more battery than I’d like, since I’m using TiddlyWiki on a somehow outdated smartphone. Or am I anticipating and asking a “premature optimization” ™ question here?