diff --git a/pass/package-lock.json b/pass/package-lock.json index 177f144d608da91aeaedfd12f7e0fd6e356e6cd7..19c20264624beec1ead705e6c81327ba1ae51b51 100644 --- a/pass/package-lock.json +++ b/pass/package-lock.json @@ -588,11 +588,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz", "integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==" }, - "node_modules/@types/offscreencanvas": { - "version": "2019.7.0", - "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz", - "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==" - }, "node_modules/@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -5091,14 +5086,6 @@ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "optional": true }, - "node_modules/qr-scanner": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/qr-scanner/-/qr-scanner-1.4.2.tgz", - "integrity": "sha512-kV1yQUe2FENvn59tMZW6mOVfpq9mGxGf8l6+EGaXUOd4RBOLg7tRC83OrirM5AtDvZRpdjdlXURsHreAOSPOUw==", - "dependencies": { - "@types/offscreencanvas": "^2019.6.4" - } - }, "node_modules/qrcode": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.1.tgz", @@ -7023,11 +7010,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz", "integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==" }, - "@types/offscreencanvas": { - "version": "2019.7.0", - "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz", - "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==" - }, "@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -10569,14 +10551,6 @@ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "optional": true }, - "qr-scanner": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/qr-scanner/-/qr-scanner-1.4.2.tgz", - "integrity": "sha512-kV1yQUe2FENvn59tMZW6mOVfpq9mGxGf8l6+EGaXUOd4RBOLg7tRC83OrirM5AtDvZRpdjdlXURsHreAOSPOUw==", - "requires": { - "@types/offscreencanvas": "^2019.6.4" - } - }, "qrcode": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.1.tgz", @@ -11801,4 +11775,4 @@ } } } -} \ No newline at end of file +} diff --git a/pass/public/styles/key/enter.less b/pass/public/styles/key/enter.less index dbfb280e3c5188ee35673f865813856178068c41..f3e5c2bf711749f3e0e10accbd55464cb2bab4df 100644 --- a/pass/public/styles/key/enter.less +++ b/pass/public/styles/key/enter.less @@ -11,50 +11,75 @@ flex-direction: column; gap: 1rem; align-items: center; - > h3 { - margin: 0; - } - > .divider { - position: relative; - display: flex; - align-items: center; - color: @font-color-on-white; - &::before, - &::after { - content: ""; - background-color: @font-color-on-white; - width: 80%; - position: absolute; - height: 1px; - display: flex; - } - &::before { - left: -100%; - } - &::after { - left: calc(100% + 20%); - } - } - > #file-selector { + > div.enter-option { width: 100%; display: flex; flex-direction: column; gap: 1rem; - > input[type="file"] { - width: 100%; - text-align: center; - &::file-selector-button { - display: none; + align-items: center; + > h3 { + margin: 0; + } + > .divider { + position: relative; + display: flex; + align-items: center; + color: @font-color-on-white; + &::before, + &::after { + content: ""; + background-color: @font-color-on-white; + width: 80%; + position: absolute; + height: 1px; + display: flex; + } + &::before { + left: -100%; + } + &::after { + left: calc(100% + 20%); } } - > label { - cursor: pointer; + > #file-selector { width: 100%; + display: flex; + flex-direction: column; + gap: 1rem; + > input[type="file"] { + width: 100%; + text-align: center; + &::file-selector-button { + display: none; + } + } + > label { + cursor: pointer; + width: 100%; + border: 1px solid #777; + padding: 0.5rem; + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.8rem; + > img { + width: 1.3rem; + } + > span { + width: 100%; + text-align: center; + } + } + } + > a { + color: inherit; + text-decoration: none; border: 1px solid #777; padding: 0.5rem; display: flex; align-items: center; gap: 0.5rem; + width: 100%; font-size: 0.8rem; > img { width: 1.3rem; @@ -64,32 +89,18 @@ text-align: center; } } - } - > a { - color: inherit; - text-decoration: none; - border: 1px solid #777; - padding: 0.5rem; - display: flex; - align-items: center; - gap: 0.5rem; - width: 100%; - font-size: 0.8rem; - > img { - width: 1.3rem; - } - > span { + > form { + display: flex; width: 100%; - text-align: center; + > #key { + flex-grow: 1; + padding: 0.5rem; + font-size: 0.75rem; + } } - } - > form { - display: flex; - width: 100%; - > #key { - flex-grow: 1; - padding: 0.5rem; - font-size: 0.75rem; + .error { + display: none; + font-size: 0.8rem; } } } @@ -110,8 +121,33 @@ > #qr-scanner { width: 100%; - > video { - width: 100%; + height: 100vh; + position: absolute; + left: 0; + top: 0; + background-color: hsla(0, 0%, 47%, 0.439); + display: none; + justify-content: center; + align-content: center; + > #scanner-container { + width: 500px; + max-width: 100vw; + max-height: 80vh; + > video { + width: 100%; + max-height: 80vh; + } + > a { + display: block; + width: 100%; + text-align: center; + background-color: rgb(255, 127, 0); + padding: 0.5rem; + margin-top: -5px; + color: white; + font-weight: bold; + text-decoration: none; + } } } } diff --git a/pass/resources/js/enter.js b/pass/resources/js/enter.js index 1c384494f157faa1585f0f92761677e94f2b212f..0743990c0aeeb4496ee3d067f7ab9c44de552707 100644 --- a/pass/resources/js/enter.js +++ b/pass/resources/js/enter.js @@ -1,18 +1,64 @@ +let scanner; +let container = document.getElementById("qr-scanner"); document.getElementById("qr-scan").addEventListener("pointerup", e => { - import('/js/qr-scanner.min.js').then(module => { + document.querySelectorAll("#scan-qr-option .error").forEach(error => { + error.style.display = "none"; + }) + import('/js/qr-scanner.min.js').then(async module => { const QrScanner = module.default; - let video = document.createElement("video"); - let container = document.getElementById("qr-scanner"); - container.appendChild(video); - let scanner = new QrScanner(video, result => console.log('decoded qr code:' + result), { + if (! await QrScanner.hasCamera()) { + document.querySelector("#qr-camera-error").style.display = "block"; + return; + } + + let video = document.querySelector("#qr-scanner > #scanner-container > video"); + let close_button = document.querySelector("#qr-scanner > #scanner-container a"); + container.style.display = "grid"; + + scanner = new QrScanner(video, result => { + scanner.stop(); + let url = result.data; + try { + url = new URL(url); + } catch (error) { + document.getElementById("qr-decode-error").style.display = "block"; + stopScanning(); + } + let key = url.searchParams.get("key"); + if (key !== null) { + location.href = "/key/" + encodeURIComponent(key); + } else { + document.getElementById("qr-decode-error").style.display = "block"; + stopScanning(); + } + }, { highlightScanRegion: true, highlightCodeOutline: true }); + close_button.addEventListener("click", e => { + e.preventDefault(); + stopScanning(); + return false; + }); + scanner.start(); }); -}); \ No newline at end of file +}); + +function stopScanning() { + scanner.stop(); + let highlights = document.querySelectorAll("#qr-scanner .scan-region-highlight"); + if (highlights) { + highlights.forEach(element => { + element.remove(); + }); + } + scanner.stop(); + scanner.destroy(); + container.style.display = "none"; +} \ No newline at end of file diff --git a/pass/routes/key.js b/pass/routes/key.js index 638ed9181519ae393e54070c2470e91fb640be20..38c0515320d3db7cbf50a94ed724835694b9e84f 100644 --- a/pass/routes/key.js +++ b/pass/routes/key.js @@ -67,7 +67,11 @@ router.post("/enter", upload.single('file'), return; } let key = url.searchParams.get("key"); - res.redirect("/key/" + encodeURIComponent(key)); + if (key !== null) { + res.redirect("/key/" + encodeURIComponent(key)); + } else { + res.render("login/key", { errors: ["Error parsing URL"] }); + } }; qr.decode(image.bitmap); }); diff --git a/pass/views/login/key.ejs b/pass/views/login/key.ejs index ec8d114bd8ee074c132353088c0f565d4dc09755..27277b2b58d4fe421b7f67b2dabf8b3586898364 100644 --- a/pass/views/login/key.ejs +++ b/pass/views/login/key.ejs @@ -3,37 +3,57 @@ <div id="enter"> <div id="form"> - <h3>Schlüssel eingeben</h3> - <form - id="key-form" - action="/key/enter" - method="post" - enctype="multipart/form-data" - > - <input type="text" name="key" id="key" placeholder="Schlüssel eingeben" /> - </form> - <div class="divider">oder</div> - <div id="file-selector"> - <label for="file"> - <img src="/images/upload.svg" alt="" /> - <span> Sicherungsdatei hochladen </span> - </label> - <input type="file" name="file" id="file" form="key-form" /> + <div id="direct-option" class="enter-option"> + <h3>Schlüssel eingeben</h3> + <form + id="key-form" + action="/key/enter" + method="post" + enctype="multipart/form-data" + > + <input + type="text" + name="key" + id="key" + placeholder="Schlüssel eingeben" + /> + </form> + </div> + <div id="file-option" class="enter-option"> + <div class="divider">oder</div> + <div id="file-selector"> + <label for="file"> + <img src="/images/upload.svg" alt="" /> + <span> Sicherungsdatei hochladen </span> + </label> + <input type="file" name="file" id="file" form="key-form" /> + </div> + </div> + <div id="scan-qr-option" class="enter-option"> + <div class="divider">oder</div> + <a href="javascript:void(0);" id="qr-scan"> + <img src="/images/camera.svg" alt="" /> + <span> QR-Code Scannen </span> + </a> + <div id="qr-camera-error" class="error">Keine Kamera verfügbar</div> + <div id="qr-decode-error" class="error"> + Qr Code enthält keine gültigen Daten. + </div> </div> - - <div class="divider">oder</div> - <a href="javascript:void(0);" id="qr-scan"> - <img src="/images/camera.svg" alt="" /> - <span> QR-Code Scannen </span> - </a> </div> + <%_ if(typeof errors !== "undefined") { _%> <div class="error"> Ein gültiger Schlüssel, oder eine gültige Sicherungsdatei ist erforderlich. </div> <%_ } _%> <button id="enter-submit" type="submit" form="key-form">Abschicken</button> - <div id="qr-scanner"></div> + <div id="qr-scanner"> + <div id="scanner-container"> + <video></video> + <a href="">Schließen</a> + </div> + </div> </div> <%- include('../templates/page_footer'); -%>