Compare commits
No commits in common. "a1982fd760c5c3215caac3cd30336bc0c089f284" and "3c8d60e61471b51f6bb4d17a251162cf04423b1e" have entirely different histories.
a1982fd760
...
3c8d60e614
2 changed files with 6 additions and 66 deletions
|
|
@ -74,11 +74,7 @@ Body `{ code }`. Sets `status=denied` and scrubs the pending secrets. → `{ ok
|
|||
- Revoke: `DELETE /api/collections/device_auth/records/{id}`
|
||||
|
||||
### `GET /device?code=CODE` (HTML)
|
||||
The self-contained approve page (`device_page.pb.js`). No external assets. The
|
||||
`?code=` (QR / direct link) is optional: opened bare, the page shows a manual
|
||||
code-entry field so the user can type the `XXXX-XXXX` code shown on the TV. The
|
||||
page normalizes (uppercase, strips the dash) before the `/info` + `/approve`
|
||||
lookups, since the code is stored dash-less.
|
||||
The self-contained approve page (`device_page.pb.js`). No external assets.
|
||||
|
||||
### Cleanup cron (`*/5 * * * *`)
|
||||
Scrubs `authToken`/`keyCiphertext`/`pollSecret`/`devicePubKey` from approved rows
|
||||
|
|
|
|||
|
|
@ -68,9 +68,6 @@ routerAdd("GET", "/device", (e) => {
|
|||
@keyframes s { to { transform:rotate(360deg); } }
|
||||
.hidden { display:none; }
|
||||
.note { margin-top:16px; font-size:12px; color:var(--muted); line-height:1.5; }
|
||||
.code-input { text-transform:uppercase; letter-spacing:.22em; text-align:center;
|
||||
font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; font-size:20px;
|
||||
font-weight:600; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -80,14 +77,6 @@ routerAdd("GET", "/device", (e) => {
|
|||
|
||||
<div id="devbox" class="dev hidden"></div>
|
||||
|
||||
<div id="codeform" class="hidden">
|
||||
<label for="codeInput">Kód ze zařízení</label>
|
||||
<input id="codeInput" class="code-input" type="text" inputmode="text"
|
||||
autocomplete="off" autocapitalize="characters" spellcheck="false"
|
||||
maxlength="9" placeholder="XXXX-XXXX" />
|
||||
<button id="codeGo" class="primary">Pokračovat</button>
|
||||
</div>
|
||||
|
||||
<div id="form">
|
||||
<label for="email">E-mail</label>
|
||||
<input id="email" type="email" autocomplete="username" inputmode="email" />
|
||||
|
|
@ -150,10 +139,6 @@ function b64d(s){var bin=atob(s),u8=new Uint8Array(bin.length);for(var i=0;i<bin
|
|||
function concat(){var n=0,i;for(i=0;i<arguments.length;i++)n+=arguments[i].length;
|
||||
var out=new Uint8Array(n),o=0;for(i=0;i<arguments.length;i++){out.set(arguments[i],o);o+=arguments[i].length;}return out;}
|
||||
function qs(name){return new URLSearchParams(location.search).get(name)||"";}
|
||||
// The human code is 8 chars shown as XXXX-XXXX but stored dash-less; normalize
|
||||
// (uppercase, drop the dash/spaces, cap at 8) before any /info + /approve lookup.
|
||||
function normCode(s){return String(s||"").toUpperCase().replace(/[^0-9A-Z]/g,"").slice(0,8);}
|
||||
function fmtCode(s){var c=normCode(s);return c.length>4?c.slice(0,4)+"-"+c.slice(4):c;}
|
||||
|
||||
async function deriveVaultKeyB64(password, saltB64){
|
||||
var salt=b64d(saltB64);
|
||||
|
|
@ -178,7 +163,7 @@ async function sealTo(tvPubB64, plaintext){
|
|||
function newSaltB64(){ return b64e(crypto.getRandomValues(new Uint8Array(16))); }
|
||||
|
||||
// ── page state ───────────────────────────────────────────────────────────────
|
||||
var CODE = normCode(qs("code"));
|
||||
var CODE = qs("code").toUpperCase().trim();
|
||||
var info = null;
|
||||
var msgEl = document.getElementById("msg");
|
||||
var approveBtn = document.getElementById("approve");
|
||||
|
|
@ -199,21 +184,12 @@ async function api(method, path, body, token){
|
|||
return { ok:r.ok, status:r.status, data:data };
|
||||
}
|
||||
|
||||
function showCodeEntry(prefill){
|
||||
document.getElementById("form").classList.add("hidden");
|
||||
document.getElementById("devbox").classList.add("hidden");
|
||||
document.getElementById("codeform").classList.remove("hidden");
|
||||
var input = document.getElementById("codeInput");
|
||||
if (prefill) input.value = fmtCode(prefill);
|
||||
input.focus();
|
||||
}
|
||||
|
||||
async function loadInfo(){
|
||||
if (!CODE){ showCodeEntry(""); setMsg("Zadejte kód zobrazený na televizi.", ""); return; }
|
||||
if (!CODE){ setMsg("Chybí kód zařízení v odkazu.", "err"); document.getElementById("form").classList.add("hidden"); return; }
|
||||
var r = await api("GET", "/api/device-auth/info?code=" + encodeURIComponent(CODE));
|
||||
if (!r.ok){
|
||||
showCodeEntry(CODE);
|
||||
setMsg("Kód nebyl nalezen nebo vypršel. Zkontrolujte ho, nebo vytvořte na televizi nový.", "err");
|
||||
setMsg("Požadavek nebyl nalezen nebo vypršel. Vytvořte na televizi nový.", "err");
|
||||
document.getElementById("form").classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
info = r.data;
|
||||
|
|
@ -222,14 +198,8 @@ async function loadInfo(){
|
|||
box.innerHTML = "Přihlásit zařízení: <b>" + (info.deviceName ? escapeHtml(info.deviceName) : "nové zařízení") + "</b>";
|
||||
if (info.status && info.status !== "pending"){
|
||||
setMsg("Tento požadavek už byl vyřízen.", "err");
|
||||
document.getElementById("codeform").classList.add("hidden");
|
||||
document.getElementById("form").classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
// code accepted → reveal the sign-in form
|
||||
document.getElementById("codeform").classList.add("hidden");
|
||||
document.getElementById("form").classList.remove("hidden");
|
||||
setMsg("");
|
||||
}
|
||||
function escapeHtml(s){ return String(s).replace(/[&<>"']/g,function(c){return {"&":"&","<":"<",">":">",'"':""","'":"'"}[c];}); }
|
||||
|
||||
|
|
@ -303,33 +273,7 @@ async function doDecline(){
|
|||
|
||||
approveBtn.addEventListener("click", doApprove);
|
||||
declineBtn.addEventListener("click", doDecline);
|
||||
|
||||
// Manual code entry: when the page is opened bare (no ?code= — e.g. the TV tells
|
||||
// the user to open …/device and type the XXXX-XXXX code). The QR/direct-link path
|
||||
// still fills CODE from the URL and skips straight to sign-in.
|
||||
var codeInput = document.getElementById("codeInput");
|
||||
var codeGo = document.getElementById("codeGo");
|
||||
|
||||
function submitCode(){
|
||||
CODE = normCode(codeInput.value);
|
||||
if (CODE.length !== 8){ setMsg("Zadejte celý kód z televize (např. 25K4-CFQP).", "err"); return; }
|
||||
setMsg("");
|
||||
loadInfo();
|
||||
}
|
||||
|
||||
codeInput.addEventListener("input", function(){
|
||||
var before = codeInput.value.length;
|
||||
codeInput.value = fmtCode(codeInput.value);
|
||||
// when the auto-dash shifts length, park the caret at the end (8-char field)
|
||||
if (codeInput.value.length !== before){
|
||||
codeInput.setSelectionRange(codeInput.value.length, codeInput.value.length);
|
||||
}
|
||||
});
|
||||
codeInput.addEventListener("keydown", function(ev){ if (ev.key === "Enter"){ ev.preventDefault(); submitCode(); } });
|
||||
codeGo.addEventListener("click", submitCode);
|
||||
|
||||
// Boot: URL code → sign-in; otherwise show the manual code-entry field.
|
||||
if (CODE){ loadInfo(); } else { showCodeEntry(""); }
|
||||
loadInfo();
|
||||
</script>
|
||||
</body>
|
||||
</html>`
|
||||
|
|
|
|||
Loading…
Reference in a new issue