rework frontend
This commit is contained in:
parent
55191c0269
commit
ad6a731fd4
9 changed files with 298 additions and 293 deletions
|
@ -1,126 +1,111 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
|
|
||||||
<html lang="">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<!--<link rel="icon" href="favicon.png" />-->
|
<!--<link rel="icon" href="favicon.png" />-->
|
||||||
<title>PassED</title>
|
<title>PassED</title>
|
||||||
|
|
||||||
<script src="/crypto.js"></script>
|
<link rel="stylesheet" href="/css/pico.min.css" />
|
||||||
<script src="/api.js"></script>
|
|
||||||
<script src="/index.js" defer></script>
|
<script src="/js/lang.js"></script>
|
||||||
<link rel="stylesheet" href="/pico.min.css" />
|
<script src="/js/api.js"></script>
|
||||||
|
<script src="/js/crypto.js"></script>
|
||||||
|
<script src="/js/main.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<nav class="container-fluid">
|
<nav class="container">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<strong>PassED</strong>
|
<strong t="title"></strong>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://git.1e99.eu/1e99/passed">Git</a>
|
<a href="https://git.1e99.eu/1e99/passed/" t="source-code"></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<details class="dropdown">
|
||||||
|
<summary t="language"></summary>
|
||||||
|
<ul dir="rtl">
|
||||||
|
<li t="language-en" class="select-language" data-lang="en"></li>
|
||||||
|
<li t="language-de" class="select-language" data-lang="de"></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<main class="container">
|
<main class="container">
|
||||||
<article>
|
<article>
|
||||||
<header>Enter Password</header>
|
<header t="enter-password"></header>
|
||||||
|
|
||||||
<form id="enter-password">
|
<form id="enter-password">
|
||||||
|
<fieldset id="enter-password">
|
||||||
<label>
|
<label>
|
||||||
Password
|
<span t="password"></span>
|
||||||
<textarea name="password"></textarea>
|
<textarea name="password"></textarea>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Expires in
|
<span t="expires-in"></span>
|
||||||
<select name="expires-in">
|
<select name="expires-in">
|
||||||
<option value="3600" selected>1 Hour</option>
|
<option value="3600" selected t="expires-in.1-hour"></option>
|
||||||
<option value="43200">12 Hours</option>
|
<option value="43200" t="expires-in.12-hours"></option>
|
||||||
<option value="86400">1 Day</option>
|
<option value="86400" t="expires-in.1-day"></option>
|
||||||
<option value="604800">1 Week</option>
|
<option value="604800" t="expires-in.1-week"></option>
|
||||||
<option value="1209600">2 Weeks</option>
|
<option value="1209600" t="expires-in.2-weeks"></option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<button type="submit">Generate URL</button>
|
<button type="submit" id="enter-password" t="generate-link"></button>
|
||||||
</form>
|
</form>
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<dialog id="url-dialog">
|
<dialog id="link">
|
||||||
<article>
|
<article>
|
||||||
<header>Password URL</header>
|
<header t="share-link"></header>
|
||||||
|
|
||||||
<fieldset role="group">
|
<fieldset role="group">
|
||||||
<input id="url" readonly autofocus />
|
<input readonly id="link"/>
|
||||||
<button id="url-copy">Copy</button>
|
<button t="copy" id="link-copy"></button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<button id="url-close">Close</button>
|
<button class="secondary" id="link-close" t="close"></button>
|
||||||
</footer>
|
|
||||||
</article>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<dialog id="view-dialog">
|
|
||||||
<article>
|
|
||||||
<header>View Password</header>
|
|
||||||
|
|
||||||
<textarea readonly id="view-password" input></textarea>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<button id="view-close">Close</button>
|
|
||||||
</footer>
|
|
||||||
</article>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<dialog id="confirm-view-dialog">
|
|
||||||
<article>
|
|
||||||
<header>View Password</header>
|
|
||||||
|
|
||||||
<p>You may only reveal the password once.</p>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<button id="view-cancel" class="secondary">
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button id="view-confirm">Confirm</button>
|
|
||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
<dialog id="not-found">
|
<dialog id="not-found">
|
||||||
<article>
|
<article>
|
||||||
<header>Password does not exist</header>
|
<header t="not-found"></header>
|
||||||
|
<p t="not-found-reason"></p>
|
||||||
<p>
|
|
||||||
The password you requested may have expired, been viewed
|
|
||||||
before or never even existed in the first place.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<button id="not-found-close" class="secondary">Close</button>
|
<button t="close" id="not-found-close"></button>
|
||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
<dialog id="loading-dialog">
|
<dialog id="confirm">
|
||||||
<h1>Loading...</h1>
|
<article>
|
||||||
|
<header t="reveal-password"></header>
|
||||||
|
<p t="reveal-password-once"></p>
|
||||||
|
<footer>
|
||||||
|
<button class="secondary" t="close" id="confirm-close"></button>
|
||||||
|
<button t="ok" id="confirm-ok"></button>
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
<dialog id="error-dialog">
|
<dialog id="view">
|
||||||
<article>
|
<article>
|
||||||
<header>Error</header>
|
<header t="password"></header>
|
||||||
|
<textarea readonly id="view-password"></textarea>
|
||||||
<textarea id="error" readonly></textarea>
|
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<button id="error-reload">Reload the page</button>
|
<button t="close" id="view-close"></button>
|
||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
218
static/index.js
218
static/index.js
|
@ -1,218 +0,0 @@
|
||||||
const loadingDialog = {
|
|
||||||
dialog: document.querySelector("dialog#loading-dialog"),
|
|
||||||
init() {
|
|
||||||
this.dialog.addEventListener("cancel", (ev) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
show() {
|
|
||||||
this.dialog.showModal();
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
this.dialog.close();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const errorDialog = {
|
|
||||||
dialog: document.querySelector("dialog#error-dialog"),
|
|
||||||
error: document.querySelector("textarea#error"),
|
|
||||||
reload: document.querySelector("button#error-reload"),
|
|
||||||
init() {
|
|
||||||
this.dialog.addEventListener("close", (ev) => {
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.reload.addEventListener("click", (ev) => {
|
|
||||||
window.location.href = "/";
|
|
||||||
});
|
|
||||||
},
|
|
||||||
show(err) {
|
|
||||||
this.error.value = err;
|
|
||||||
console.error(err);
|
|
||||||
this.dialog.showModal();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const urlDialog = {
|
|
||||||
dialog: document.querySelector("dialog#url-dialog"),
|
|
||||||
url: document.querySelector("input#url"),
|
|
||||||
urlCopy: document.querySelector("button#url-copy"),
|
|
||||||
close: document.querySelector("button#url-close"),
|
|
||||||
init() {
|
|
||||||
this.dialog.addEventListener("close", (ev) => {});
|
|
||||||
|
|
||||||
this.urlCopy.addEventListener("click", (ev) => {
|
|
||||||
this.url.select();
|
|
||||||
this.url.setSelectionRange(0, 99999);
|
|
||||||
navigator.clipboard.writeText(this.url.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.close.addEventListener("click", (ev) => {
|
|
||||||
this.dialog.close();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
show(url) {
|
|
||||||
this.url.value = url;
|
|
||||||
this.dialog.showModal();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const notFoundDialog = {
|
|
||||||
dialog: document.querySelector("dialog#not-found"),
|
|
||||||
close: document.querySelector("button#not-found-close"),
|
|
||||||
init() {
|
|
||||||
this.dialog.addEventListener("close", (ev) => {
|
|
||||||
window.location.hash = "";
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.close.addEventListener("click", (ev) => {
|
|
||||||
window.location.hash = "";
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
show() {
|
|
||||||
this.dialog.showModal();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const viewDialog = {
|
|
||||||
dialog: document.querySelector("dialog#view-dialog"),
|
|
||||||
password: document.querySelector("textarea#view-password"),
|
|
||||||
close: document.querySelector("button#view-close"),
|
|
||||||
init() {
|
|
||||||
this.dialog.addEventListener("close", (ev) => {
|
|
||||||
window.location.hash = "";
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.close.addEventListener("click", (ev) => {
|
|
||||||
this.dialog.close();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
show(password) {
|
|
||||||
this.password.value = password;
|
|
||||||
this.dialog.showModal();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmViewDialog = {
|
|
||||||
dialog: document.querySelector("dialog#confirm-view-dialog"),
|
|
||||||
cancel: document.querySelector("button#view-cancel"),
|
|
||||||
confirm: document.querySelector("button#view-confirm"),
|
|
||||||
resolve: null,
|
|
||||||
reject: null,
|
|
||||||
init() {
|
|
||||||
this.dialog.addEventListener("cancel", (ev) => {
|
|
||||||
this.dialog.close();
|
|
||||||
this.resolve(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.cancel.addEventListener("click", (ev) => {
|
|
||||||
this.dialog.close();
|
|
||||||
this.resolve(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.confirm.addEventListener("click", (ev) => {
|
|
||||||
this.dialog.close();
|
|
||||||
this.resolve(true);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
show() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.resolve = resolve;
|
|
||||||
this.reject = reject;
|
|
||||||
this.dialog.showModal();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
async function viewPassword() {
|
|
||||||
try {
|
|
||||||
loadingDialog.show();
|
|
||||||
|
|
||||||
let id, key, iv;
|
|
||||||
|
|
||||||
// We need to be backwards compatible with old links that still use the query
|
|
||||||
if (window.location.search.trim() != "") {
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
id = params.get("id");
|
|
||||||
key = params.get("key");
|
|
||||||
iv = params.get("iv");
|
|
||||||
} else {
|
|
||||||
// Need to remove leading "#"
|
|
||||||
const hash = window.location.hash.substring(1);
|
|
||||||
const split = hash.split(":");
|
|
||||||
id = split[0];
|
|
||||||
key = split[1];
|
|
||||||
iv = split[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
const exists = await hasPassword(id);
|
|
||||||
if (!exists) {
|
|
||||||
notFoundDialog.show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const shouldView = await confirmViewDialog.show();
|
|
||||||
if (!shouldView) {
|
|
||||||
// This is needed for the redirect, otherwise the user won't get redirected
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.hash = "";
|
|
||||||
window.location.reload();
|
|
||||||
}, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const encrypted = await getPassword(id);
|
|
||||||
const password = await decryptPassword(encrypted, key, iv);
|
|
||||||
|
|
||||||
viewDialog.show(password);
|
|
||||||
} catch (error) {
|
|
||||||
errorDialog.show(error);
|
|
||||||
} finally {
|
|
||||||
loadingDialog.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const enterPassword = document.querySelector("form#enter-password");
|
|
||||||
|
|
||||||
enterPassword.addEventListener("submit", async (ev) => {
|
|
||||||
try {
|
|
||||||
loadingDialog.show();
|
|
||||||
ev.preventDefault();
|
|
||||||
const data = new FormData(ev.target);
|
|
||||||
|
|
||||||
const password = await encryptPassword(data.get("password"));
|
|
||||||
const id = await uploadPassword(
|
|
||||||
password.password,
|
|
||||||
parseInt(data.get("expires-in")),
|
|
||||||
);
|
|
||||||
|
|
||||||
const url = new URL(window.location);
|
|
||||||
url.hash = [id, password.key, password.iv].join(":");
|
|
||||||
urlDialog.show(url.toString());
|
|
||||||
} catch (error) {
|
|
||||||
errorDialog.show(error);
|
|
||||||
} finally {
|
|
||||||
loadingDialog.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
loadingDialog.init();
|
|
||||||
errorDialog.init();
|
|
||||||
urlDialog.init();
|
|
||||||
notFoundDialog.init();
|
|
||||||
viewDialog.init();
|
|
||||||
confirmViewDialog.init();
|
|
||||||
|
|
||||||
const hash = window.location.hash;
|
|
||||||
if (hash.trim() != "") {
|
|
||||||
viewPassword();
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to be backwards compatible with the old links
|
|
||||||
const query = window.location.search;
|
|
||||||
if (query.trim() != "") {
|
|
||||||
viewPassword();
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
/** Upload a password to the server
|
/** Upload a password to the server
|
||||||
* @param {string} password Encryptes password
|
* @param {string} password Encryptes password
|
||||||
* @param {Promise<number>} expiresIn Number of seconds in which the password will expire
|
* @param {number} expiresIn Number of seconds in which the password will expire
|
||||||
|
* @returns {Promise<string>}
|
||||||
*/
|
*/
|
||||||
async function uploadPassword(password, expiresIn) {
|
async function uploadPassword(password, expiresIn) {
|
||||||
const res = await fetch("/api/password", {
|
const res = await fetch("/api/password", {
|
||||||
|
@ -36,6 +37,7 @@ async function hasPassword(id) {
|
||||||
|
|
||||||
throw new Error(`Failed to check if password exists: ${res.status}: ${msg}`);
|
throw new Error(`Failed to check if password exists: ${res.status}: ${msg}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get a password from the server
|
/** Get a password from the server
|
||||||
* @param {string} id Password ID
|
* @param {string} id Password ID
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
70
static/js/lang.js
Normal file
70
static/js/lang.js
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
const LANGUAGE_KEY = "language";
|
||||||
|
const languages = {};
|
||||||
|
let language = "en";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} lang
|
||||||
|
*/
|
||||||
|
async function setLanguage(lang) {
|
||||||
|
if (languages[lang] == undefined) {
|
||||||
|
const res = await fetch(`/lang/${lang}.json`);
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
languages[lang] = json;
|
||||||
|
}
|
||||||
|
|
||||||
|
language = lang;
|
||||||
|
translatePage();
|
||||||
|
localStorage.setItem(LANGUAGE_KEY, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Translates a key
|
||||||
|
* @param {string} key
|
||||||
|
* @param {any} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function translate(key, ...args) {
|
||||||
|
const bundle = languages[language];
|
||||||
|
let value = bundle[key];
|
||||||
|
if (value == undefined) {
|
||||||
|
value = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
value = value.replaceAll(`{${i}}`, args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function translatePage() {
|
||||||
|
const elements = document.getElementsByTagName("*");
|
||||||
|
for (const element of elements) {
|
||||||
|
const key = element.getAttribute("t");
|
||||||
|
if (key == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
element.innerHTML = translate(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initLanguageButtons() {
|
||||||
|
const buttons = document.querySelectorAll("li.select-language");
|
||||||
|
for (const button of buttons) {
|
||||||
|
button.addEventListener("click", async (ev) => {
|
||||||
|
const self = ev.target;
|
||||||
|
const lang = self.getAttribute("data-lang");
|
||||||
|
await setLanguage(lang);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("load", async () => {
|
||||||
|
const lang = localStorage.getItem(LANGUAGE_KEY);
|
||||||
|
if (lang != null) {
|
||||||
|
await setLanguage(lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
initLanguageButtons();
|
||||||
|
});
|
118
static/js/main.js
Normal file
118
static/js/main.js
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
function initEnterPassword() {
|
||||||
|
const form = document.querySelector("form#enter-password");
|
||||||
|
const fieldset = document.querySelector("fieldset#enter-password");
|
||||||
|
const submit = document.querySelector("button#enter-password");
|
||||||
|
|
||||||
|
const dialog = document.querySelector("dialog#link");
|
||||||
|
const link = document.querySelector("input#link");
|
||||||
|
const copy = document.querySelector("button#link-copy");
|
||||||
|
const close = document.querySelector("button#link-close");
|
||||||
|
|
||||||
|
form.addEventListener("submit", async (ev) => {
|
||||||
|
const data = new FormData(ev.target);
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
try {
|
||||||
|
fieldset.disabled = true;
|
||||||
|
submit.ariaBusy = "true";
|
||||||
|
|
||||||
|
const password = await encryptPassword(data.get("password"));
|
||||||
|
|
||||||
|
const id = await uploadPassword(
|
||||||
|
password.password,
|
||||||
|
parseInt(data.get("expires-in")),
|
||||||
|
);
|
||||||
|
|
||||||
|
const url = new URL(window.location.toString());
|
||||||
|
url.hash = [id, password.key, password.iv].join(":");
|
||||||
|
|
||||||
|
link.value = url.toString();
|
||||||
|
dialog.showModal();
|
||||||
|
} finally {
|
||||||
|
fieldset.disabled = false;
|
||||||
|
submit.ariaBusy = "false";
|
||||||
|
ev.target.reset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.addEventListener("close", (ev) => {
|
||||||
|
this.link.value = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
copy.addEventListener("click", (ev) => {
|
||||||
|
link.select();
|
||||||
|
link.setSelectionRange(0, 99999);
|
||||||
|
navigator.clipboard.writeText(link.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
close.addEventListener("click", (ev) => {
|
||||||
|
dialog.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmViewPassword() {
|
||||||
|
const dialog = document.querySelector("dialog#confirm");
|
||||||
|
const close = document.querySelector("button#confirm-close");
|
||||||
|
const ok = document.querySelector("button#confirm-ok");
|
||||||
|
const notFoundDialog = document.querySelector("dialog#not-found");
|
||||||
|
const notFoundClose = document.querySelector("button#not-found-close");
|
||||||
|
|
||||||
|
let hash = window.location.hash;
|
||||||
|
hash = hash.substring(1);
|
||||||
|
if (hash.trim() == "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [id, key, iv] = hash.split(":");
|
||||||
|
const has = await hasPassword(id);
|
||||||
|
if (!has) {
|
||||||
|
notFoundClose.addEventListener("click", () => notFoundDialog.close());
|
||||||
|
notFoundDialog.addEventListener("close", () => (window.location.hash = ""));
|
||||||
|
|
||||||
|
notFoundDialog.showModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.addEventListener("close", () => (window.location.hash = ""));
|
||||||
|
|
||||||
|
close.addEventListener("click", () => {
|
||||||
|
dialog.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
ok.addEventListener("click", async () => {
|
||||||
|
try {
|
||||||
|
close.disabled = true;
|
||||||
|
ok.disabled = true;
|
||||||
|
ok.ariaBusy = "true";
|
||||||
|
|
||||||
|
const encryptedPassword = await getPassword(id);
|
||||||
|
const password = await decryptPassword(encryptedPassword, key, iv);
|
||||||
|
|
||||||
|
viewPassword(password);
|
||||||
|
} finally {
|
||||||
|
close.disabled = false;
|
||||||
|
ok.disabled = false;
|
||||||
|
ok.ariaBusy = "false";
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function viewPassword(password) {
|
||||||
|
const dialog = document.querySelector("dialog#view");
|
||||||
|
const viewPassword = document.querySelector("textarea#view-password");
|
||||||
|
const close = document.querySelector("button#view-close");
|
||||||
|
|
||||||
|
close.addEventListener("click", () => dialog.close());
|
||||||
|
dialog.addEventListener("close", () => (window.location.hash = ""), 0);
|
||||||
|
|
||||||
|
viewPassword.value = password;
|
||||||
|
dialog.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
initEnterPassword();
|
||||||
|
confirmViewPassword();
|
||||||
|
});
|
24
static/lang/de.json
Normal file
24
static/lang/de.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"title": "PassED",
|
||||||
|
"source-code": "Quellcode",
|
||||||
|
"language": "Sprache",
|
||||||
|
"language-en": "Englisch",
|
||||||
|
"language-de": "Deutsch",
|
||||||
|
"enter-password": "Passwort eingeben",
|
||||||
|
"password": "Passwort",
|
||||||
|
"expires-in": "Ablaufzeit",
|
||||||
|
"expires-in.1-hour": "1 Stunde",
|
||||||
|
"expires-in.12-hours": "12 Stunden",
|
||||||
|
"expires-in.1-day": "1 Tag",
|
||||||
|
"expires-in.1-week": "1 Woche",
|
||||||
|
"expires-in.2-weeks": "2 Wochen",
|
||||||
|
"generate-link": "Link erstellen",
|
||||||
|
"share-link": "Link teilen",
|
||||||
|
"not-found": "Passwort wurde nicht gefunden",
|
||||||
|
"not-found-reason": "Das von Ihnen angefragte Passwort ist vielleicht abgelaufen, wurde bereits angeschaut oder hat noch nie existiert.",
|
||||||
|
"reveal-password": "Passwort anschauen",
|
||||||
|
"reveal-password-once": "Sie können sich das Passwort nur einmal anschauen.",
|
||||||
|
"copy": "Kopieren",
|
||||||
|
"close": "Schließen",
|
||||||
|
"ok": "OK"
|
||||||
|
}
|
24
static/lang/en.json
Normal file
24
static/lang/en.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"title": "PassED",
|
||||||
|
"source-code": "Source",
|
||||||
|
"language": "Language",
|
||||||
|
"language-en": "English",
|
||||||
|
"language-de": "German",
|
||||||
|
"enter-password": "Enter Password",
|
||||||
|
"password": "Password",
|
||||||
|
"expires-in": "Expires in",
|
||||||
|
"expires-in.1-hour": "1 Hour",
|
||||||
|
"expires-in.12-hours": "2 Hours",
|
||||||
|
"expires-in.1-day": "1 Day",
|
||||||
|
"expires-in.1-week": "1 Week",
|
||||||
|
"expires-in.2-weeks": "2 Weeks",
|
||||||
|
"generate-link": "Generate Link",
|
||||||
|
"share-link": "Share Link",
|
||||||
|
"not-found": "Password not found",
|
||||||
|
"not-found-reason": "The password you requested may have expired, been viewed before or never even existed in the first place.",
|
||||||
|
"reveal-password": "Reveal Password",
|
||||||
|
"reveal-password-once": "You may only reveal the password once",
|
||||||
|
"copy": "Copy",
|
||||||
|
"close": "Close",
|
||||||
|
"ok": "OK"
|
||||||
|
}
|
Loading…
Reference in a new issue