From 1e5e6cf7dd4054749b3757e493b309f3a5d665e6 Mon Sep 17 00:00:00 2001 From: robinmoisson Date: Tue, 13 Feb 2024 21:58:59 +0100 Subject: [PATCH] add --share-remember to remember the password in shared links closes #183, closes #184. Thank you @uzadude! --- README.md | 7 +++++++ cli/helpers.js | 5 +++++ cli/index.js | 9 +++++++-- example/encrypted/example.html | 30 +++++++++++++++++++++--------- index.html | 28 ++++++++++++++++++++-------- lib/staticryptJs.js | 28 ++++++++++++++++++++-------- package.json | 2 +- 7 files changed, 81 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index b6313b1..853afd8 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,11 @@ staticrypt dir_to_encrypt/* -r -d dir_to_encrypt # you can also pass '--share' without specifying the URL to get the `#staticrypt_pwd=...` staticrypt test.html --share https://example.com/encrypted.html # => https://example.com/encrypted.html#staticrypt_pwd=5bfbf1343c7257cd7be23ecd74bb37fa2c76d041042654f358b6255baeab898f + +# add --share-remember to auto-enable "Remember-me" - useful if you want send one link to autodecrypt multiple pages +# (you can also just append '&remember_me') +staticrypt test.html --share --share-remember +# => #staticrypt_pwd=5bfbf1343c7257cd7be23ecd74bb37fa2c76d041042654f358b6255baeab898f&remember_me ``` **Pin the salt to use staticrypt in your CI in a build step** - if you want want the "Remember-me" or share features to work accross multiple pages or multiple successive deployment, the salt needs to stay the same ([see why](https://github.com/robinmoisson/staticrypt#why-does-staticrypt-create-a-config-file)). If you run StatiCrypt in a CI step, you can pin the salt in two ways: @@ -152,6 +157,8 @@ The password argument is optional if `STATICRYPT_PASSWORD` is set in the environ as a value to append "#staticrypt_pwd=", or leave empty to display the hash to append. [string] + --share-remember Whether the share link should auto-enable + 'Remember-me'. [boolean] [default: false] --short Hide the "short password" warning. [boolean] [default: false] -t, --template Path to custom HTML template with password diff --git a/cli/helpers.js b/cli/helpers.js index d21b781..5291146 100644 --- a/cli/helpers.js +++ b/cli/helpers.js @@ -426,6 +426,11 @@ function parseCommandLineArguments() { '"#staticrypt_pwd=", or leave empty to display the hash to append.', type: "string", }) + .option("share-remember", { + type: "boolean", + describe: "Whether the share link should auto-enable 'Remember-me'.", + default: false, + }) .option("short", { describe: 'Hide the "short password" warning.', type: "boolean", diff --git a/cli/index.js b/cli/index.js index 9140637..9245e52 100755 --- a/cli/index.js +++ b/cli/index.js @@ -86,9 +86,14 @@ async function runStatiCrypt() { if (hasShareFlag) { await validatePassword(password, namedArgs.short); - const url = namedArgs.share || ""; + let url = namedArgs.share || ""; + url += "#staticrypt_pwd=" + hashedPassword; - console.log(url + "#staticrypt_pwd=" + hashedPassword); + if (namedArgs.shareRemember) { + url += `&remember_me`; + } + + console.log(url); return; } diff --git a/example/encrypted/example.html b/example/encrypted/example.html index 90e430a..4113448 100644 --- a/example/encrypted/example.html +++ b/example/encrypted/example.html @@ -610,11 +610,17 @@ function init(staticryptConfig, templateConfig) { * expose more information in the future we can do it without breaking the password_template */ async function handleDecryptionOfPage(password, isRememberChecked) { - const { isRememberEnabled, rememberDurationInDays, staticryptSaltUniqueVariableName } = staticryptConfig; - const { rememberExpirationKey, rememberPassphraseKey } = templateConfig; + const { staticryptSaltUniqueVariableName } = staticryptConfig; // decrypt and replace the whole page const hashedPassword = await cryptoEngine.hashPassword(password, staticryptSaltUniqueVariableName); + return handleDecryptionOfPageFromHash(hashedPassword, isRememberChecked); + } + exports.handleDecryptionOfPage = handleDecryptionOfPage; + + async function handleDecryptionOfPageFromHash(hashedPassword, isRememberChecked) { + const { isRememberEnabled, rememberDurationInDays } = staticryptConfig; + const { rememberExpirationKey, rememberPassphraseKey } = templateConfig; const isDecryptionSuccessful = await decryptAndReplaceHtml(hashedPassword); @@ -643,7 +649,7 @@ function init(staticryptConfig, templateConfig) { hashedPassword, }; } - exports.handleDecryptionOfPage = handleDecryptionOfPage; + exports.handleDecryptionOfPageFromHash = handleDecryptionOfPageFromHash; /** * Clear localstorage from staticrypt related values @@ -740,21 +746,27 @@ function init(staticryptConfig, templateConfig) { return false; } - function decryptOnLoadFromUrl() { + async function decryptOnLoadFromUrl() { const passwordKey = "staticrypt_pwd"; + const rememberMeKey = "remember_me"; - // get the password from the query param + // try to get the password from the query param (for backward compatibility - we now want to avoid this method, + // since it sends the hashed password to the server which isn't needed) const queryParams = new URLSearchParams(window.location.search); const hashedPasswordQuery = queryParams.get(passwordKey); + const rememberMeQuery = queryParams.get(rememberMeKey); + const urlFragment = window.location.hash.substring(1); // get the password from the url fragment - const hashRegexMatch = window.location.hash.substring(1).match(new RegExp(passwordKey + "=(.*)")); - const hashedPasswordFragment = hashRegexMatch ? hashRegexMatch[1] : null; + const hashedPasswordRegexMatch = urlFragment.match(new RegExp(passwordKey + "=([^&]*)")); + const hashedPasswordFragment = hashedPasswordRegexMatch ? hashedPasswordRegexMatch[1] : null; + const rememberMeFragment = urlFragment.includes(rememberMeKey); const hashedPassword = hashedPasswordFragment || hashedPasswordQuery; + const rememberMe = rememberMeFragment || rememberMeQuery; if (hashedPassword) { - return decryptAndReplaceHtml(hashedPassword); + return handleDecryptionOfPageFromHash(hashedPassword, rememberMe); } return false; @@ -768,7 +780,7 @@ exports.init = init; })()); const templateError = "Bad password!", isRememberEnabled = true, - staticryptConfig = {"staticryptEncryptedMsgUniqueVariableName":"da67de28fb1fec9f04aaf615aa0e80a9675a43473e21b51a1dee72cb6153728dde06e9c242c6de08ab4608d18e73c9f922466a4695fe60e8f684f7d5d7b54149d7749bc2e626e39844d6757289f17696ea7992d1c87b49f76903c57d6506bb5aee0f0cdf4183bb20f71ad0f719c5a3ee338a486c70a1d433c00fd4384c2ec65471c436dc4cf0c4ba13a2189db1ee335f113652439c1feb2c02bf237ddada4ae46bd404f3f8417cc6b27d2e7960024afe2323edb19e8f38c699b9f4ce0db36d37","isRememberEnabled":true,"rememberDurationInDays":0,"staticryptSaltUniqueVariableName":"b93bbaf35459951c47721d1f3eaeb5b9"}; + staticryptConfig = {"staticryptEncryptedMsgUniqueVariableName":"0aebc39457f31c757cef31f0fa9b2fb0cb4ebe9845a3690ab119002ae21ce5b7b200ded81ad6da56d0f6b98df029102c0913440135cd3f75b84f481ee32aab034c0bb3055168d6c49afd4b59e7189b539b573e6effbd29e403ef3234ab8bd1c2de1cd97a4f94e88c4d632f8648e9d99c7d988723634ce805d021d1d017c3e125e98e58914db31c4cca5a0f6b1f4464d284a48548a1eb5edad8f910aea4d2beee6b811785b556c7ec67c48112f551aa860614faf34887267c6119feda894b3ed8","isRememberEnabled":true,"rememberDurationInDays":0,"staticryptSaltUniqueVariableName":"b93bbaf35459951c47721d1f3eaeb5b9"}; // you can edit these values to customize some of the behavior of StatiCrypt const templateConfig = { diff --git a/index.html b/index.html index 7530426..c30dd13 100644 --- a/index.html +++ b/index.html @@ -1101,11 +1101,17 @@ function init(staticryptConfig, templateConfig) { * expose more information in the future we can do it without breaking the password_template */ async function handleDecryptionOfPage(password, isRememberChecked) { - const { isRememberEnabled, rememberDurationInDays, staticryptSaltUniqueVariableName } = staticryptConfig; - const { rememberExpirationKey, rememberPassphraseKey } = templateConfig; + const { staticryptSaltUniqueVariableName } = staticryptConfig; // decrypt and replace the whole page const hashedPassword = await cryptoEngine.hashPassword(password, staticryptSaltUniqueVariableName); + return handleDecryptionOfPageFromHash(hashedPassword, isRememberChecked); + } + exports.handleDecryptionOfPage = handleDecryptionOfPage; + + async function handleDecryptionOfPageFromHash(hashedPassword, isRememberChecked) { + const { isRememberEnabled, rememberDurationInDays } = staticryptConfig; + const { rememberExpirationKey, rememberPassphraseKey } = templateConfig; const isDecryptionSuccessful = await decryptAndReplaceHtml(hashedPassword); @@ -1134,7 +1140,7 @@ function init(staticryptConfig, templateConfig) { hashedPassword, }; } - exports.handleDecryptionOfPage = handleDecryptionOfPage; + exports.handleDecryptionOfPageFromHash = handleDecryptionOfPageFromHash; /** * Clear localstorage from staticrypt related values @@ -1231,21 +1237,27 @@ function init(staticryptConfig, templateConfig) { return false; } - function decryptOnLoadFromUrl() { + async function decryptOnLoadFromUrl() { const passwordKey = "staticrypt_pwd"; + const rememberMeKey = "remember_me"; - // get the password from the query param + // try to get the password from the query param (for backward compatibility - we now want to avoid this method, + // since it sends the hashed password to the server which isn't needed) const queryParams = new URLSearchParams(window.location.search); const hashedPasswordQuery = queryParams.get(passwordKey); + const rememberMeQuery = queryParams.get(rememberMeKey); + const urlFragment = window.location.hash.substring(1); // get the password from the url fragment - const hashRegexMatch = window.location.hash.substring(1).match(new RegExp(passwordKey + "=(.*)")); - const hashedPasswordFragment = hashRegexMatch ? hashRegexMatch[1] : null; + const hashedPasswordRegexMatch = urlFragment.match(new RegExp(passwordKey + "=([^&]*)")); + const hashedPasswordFragment = hashedPasswordRegexMatch ? hashedPasswordRegexMatch[1] : null; + const rememberMeFragment = urlFragment.includes(rememberMeKey); const hashedPassword = hashedPasswordFragment || hashedPasswordQuery; + const rememberMe = rememberMeFragment || rememberMeQuery; if (hashedPassword) { - return decryptAndReplaceHtml(hashedPassword); + return handleDecryptionOfPageFromHash(hashedPassword, rememberMe); } return false; diff --git a/lib/staticryptJs.js b/lib/staticryptJs.js index d5c1559..3e04532 100644 --- a/lib/staticryptJs.js +++ b/lib/staticryptJs.js @@ -63,11 +63,17 @@ function init(staticryptConfig, templateConfig) { * expose more information in the future we can do it without breaking the password_template */ async function handleDecryptionOfPage(password, isRememberChecked) { - const { isRememberEnabled, rememberDurationInDays, staticryptSaltUniqueVariableName } = staticryptConfig; - const { rememberExpirationKey, rememberPassphraseKey } = templateConfig; + const { staticryptSaltUniqueVariableName } = staticryptConfig; // decrypt and replace the whole page const hashedPassword = await cryptoEngine.hashPassword(password, staticryptSaltUniqueVariableName); + return handleDecryptionOfPageFromHash(hashedPassword, isRememberChecked); + } + exports.handleDecryptionOfPage = handleDecryptionOfPage; + + async function handleDecryptionOfPageFromHash(hashedPassword, isRememberChecked) { + const { isRememberEnabled, rememberDurationInDays } = staticryptConfig; + const { rememberExpirationKey, rememberPassphraseKey } = templateConfig; const isDecryptionSuccessful = await decryptAndReplaceHtml(hashedPassword); @@ -96,7 +102,7 @@ function init(staticryptConfig, templateConfig) { hashedPassword, }; } - exports.handleDecryptionOfPage = handleDecryptionOfPage; + exports.handleDecryptionOfPageFromHash = handleDecryptionOfPageFromHash; /** * Clear localstorage from staticrypt related values @@ -193,21 +199,27 @@ function init(staticryptConfig, templateConfig) { return false; } - function decryptOnLoadFromUrl() { + async function decryptOnLoadFromUrl() { const passwordKey = "staticrypt_pwd"; + const rememberMeKey = "remember_me"; - // get the password from the query param + // try to get the password from the query param (for backward compatibility - we now want to avoid this method, + // since it sends the hashed password to the server which isn't needed) const queryParams = new URLSearchParams(window.location.search); const hashedPasswordQuery = queryParams.get(passwordKey); + const rememberMeQuery = queryParams.get(rememberMeKey); + const urlFragment = window.location.hash.substring(1); // get the password from the url fragment - const hashRegexMatch = window.location.hash.substring(1).match(new RegExp(passwordKey + "=(.*)")); - const hashedPasswordFragment = hashRegexMatch ? hashRegexMatch[1] : null; + const hashedPasswordRegexMatch = urlFragment.match(new RegExp(passwordKey + "=([^&]*)")); + const hashedPasswordFragment = hashedPasswordRegexMatch ? hashedPasswordRegexMatch[1] : null; + const rememberMeFragment = urlFragment.includes(rememberMeKey); const hashedPassword = hashedPasswordFragment || hashedPasswordQuery; + const rememberMe = rememberMeFragment || rememberMeQuery; if (hashedPassword) { - return decryptAndReplaceHtml(hashedPassword); + return handleDecryptionOfPageFromHash(hashedPassword, rememberMe); } return false; diff --git a/package.json b/package.json index acfe243..6bcca54 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "staticrypt", - "version": "3.3.2", + "version": "3.4.0", "description": "Baed on the [crypto-js](https://github.com/brix/crypto-js) library, StatiCrypt uses AES-256 to encrypt your input with your long password and put it in a HTML file with a password prompt that can decrypted in-browser (client side).", "main": "index.js", "files": [