const $ = van.tags; const writeKey = "vault"; const encrypt =(secret)=> { const serialized = JSON.stringify(vault.val); // todo add actual encryption here using secret const encrypted = serialized+"|"+secret; // localStorage.setItem(writeKey, encrypted); vaultSerialized.val = encrypted; }; const decrypt =(secret)=> { try { const encrypted = localStorage.getItem(writeKey)||""; vaultSerialized.val = encrypted; // todo add actual decryption here using secret const [decrypted, actual] = encrypted.split("|"); if(actual !== secret){return false;} // vault.val = JSON.parse(decrypted); return true; } catch(e) { console.log("decrypt error", e); return false; } }; const copyToClipboard =(str)=> { alert("copied to clipboard") // todo copy string to clipboard; } /** @typedef {[domain:string, username:string, password:string, codegen:string, timestamp:number, custom_kvp:Record]} CredSet */ const vault = van.state(/**@type{CredSet[]}*/([])); const vaultSerialized = van.state(""); const password = van.state(""); const attemptsFailed = van.state(0); const preexisting = (localStorage.getItem(writeKey)||""); const editCred = van.state(/**@type{null|CredSet}*/null); const editInd = van.state(0); const PasswordChange =()=> { if(preexisting && attemptsFailed.val > -1) { const success = decrypt(password.val); if(success) { attemptsFailed.val = -1; } else { attemptsFailed.val = attemptsFailed.val+1; return; } } encrypt(password.val); console.log("password change"); }; /** @type {(credSet:CredSet)=>void} */ const VaultAdd =(credSet)=>{ const clone = [...vault.rawVal]; let i; for(i=0; iHTMLFieldSetElement} */ const block =(label, control)=> { control.id = label; return $.fieldset( $.label({for:label}, label), control ) }; const inputSite = $.input(); const inputUser = $.input(); const inputPass = $.input(); const inputCode = $.input(); return $.form( { onsubmit(e){ e.preventDefault(); VaultAdd([inputSite.value, inputUser.value, inputPass.value, inputCode.value, new Date().getTime(), {}]) } }, block("Site", inputSite), block("User", inputUser), block("Pass", inputPass), block("Code", inputCode), $.button("Add") ) }, /**@type {(label:string, cred:CredSet, credIndex:Util.Indices)=>HTMLFieldSetElement} */ SingleFieldForm(label, cred, credIndex){ const edit = van.state(false); const show = van.state(false); return $.fieldset( $.label( {onclick(){ if(!edit.rawVal) { copyToClipboard() } }}, label), $.button({onclick(){editCred.val = cred; editInd.val = credIndex;}}, "Edit") //()=> $.button({onclick(){show.val = !show.val}}, show.val ? "Hide":"Show"), //()=> $.button({onclick(){edit.val = !edit.val}}, edit.val ? "Cancel":"Edit"), //()=> edit.val ? $.input({type:"text", value:cred[credIndex].toString()}) : null, //()=> edit.val ? $.button({onclick(){}}, "Save") : null, ) }, /**@type {(cred:CredSet)=>HTMLDivElement} */ ExistingForm(cred) { return $.div( $.details( $.summary( $.h2(cred[0]), ), Components.SingleFieldForm("User", cred, 1), Components.SingleFieldForm("Pass", cred, 2), $.details( $.summary("Delete this record"), $.button("Delete") ) ) ) }, Root() { const input = $.input({id:"passwordField", value:password.val}); const status =()=> { let message = ""; let showVault = false; let button = "Re-encrypt"; if(preexisting) { if(attemptsFailed.val > -1) { message = `Existing session data found. Enter the correct password to recover it.`; button = `Recover`; if(attemptsFailed.val > 0) { message += ` Password Incorrect (${attemptsFailed.val} attempts).`; } } else { message = `Password changes will re-encrypt your active session data.`; showVault = true; } } else { message = `You are starting a new session.`; button = `Encrypt`; showVault = true; } return [message, button, showVault] } const [message, buttonText, showVault] = status(); return $.div( $.h1(message), $.label({for:input.id}, "Password"), ()=>input, $.button({onclick(){ password.val = input.value; PasswordChange(); }}, buttonText), $.div( $.h3("debug zone"), $.ul( $.li($.strong("preexisting "), preexisting), $.li($.strong("attemptsFailed "), attemptsFailed.val), $.li($.strong("vaultSerialized "), vaultSerialized.val), $.li($.strong("password "), password.val), ) ), $.hr(), showVault ? Components.Vault() : null ); }, Modal() { if(editCred.val) { const input = $.input({value:editCred.val[editInd.val]}); return $.div( input, $.button({onclick(){ editCred.val[editInd.val] = input.value; editCred.val = null; encrypt(password.val); }}, "Save"), $.button({onclick(){ editCred.val = null; }}, "Cancel") ) } else { return "no compoent" } }, Vault() { return $.div( $.h2("Vault"), Components.Form, Components.Modal, vault.val.map(Components.ExistingForm) ); } } van.add(document.body, Components.Root);