266 lines
7.1 KiB
JavaScript
266 lines
7.1 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;
|
|
}
|
|
};
|
|
|
|
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<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 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; 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")
|
|
)
|
|
},
|
|
|
|
/**@type {(label:string, cred:CredSet, credIndex:Util.Indices<CredSet>)=>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); |