cred-vault/app.js

191 lines
5.0 KiB
JavaScript

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;
}
};
/** @typedef {[domain:string, username:string, password:string, codegen:string, timestamp:number, custom_kvp:Record<string, string>]} 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 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; i<clone.length; i++)
{
if(credSet[0] < clone[i][0])
{
break;
}
}
clone.splice(i, 0, credSet);
vault.val = clone;
encrypt(password.val);
};
const Components = {
Form()
{
/** @type {(label:string, control:HTMLInputElement)=>HTMLFieldSetElement} */
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")
)
},
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.`;
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
);
},
Vault()
{
return $.div(
$.h2("Vault"),
Components.Form,
vault.val.map((cred)=>{
return $.details(
$.summary(cred[0]),
$.p(
$.strong("User Name:"),
cred[1]
)
)
})
);
}
}
van.add(document.body, Components.Root);