1 line
13 KiB
Plaintext
1 line
13 KiB
Plaintext
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="referrer" content="no-referrer"> <title>Sign in</title> <style>*,::after,::before{box-sizing:border-box;margin:0;padding:0}:root{--bg:#f5f5f4;--card:#fff;--border:#e5e5e3;--text:#1a1a18;--muted:#6b6b67;--accent:#1a1a18;--accent-fg:#fff;--info:#185fa5;--success:#0f7a3c;--radius:10px;--input-bg:#fafaf9}@media(prefers-color-scheme:dark){:root{--bg:#18181b;--card:#1f1f23;--border:#2e2e33;--text:#f4f4f2;--muted:#9b9b95;--accent:#f4f4f2;--accent-fg:#18181b;--info:#5faee3;--success:#58c98a;--input-bg:#2a2a2e}}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:1rem}.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);padding:.5rem 2.25rem 2rem;width:100%;max-width:384px}.logo-wrap{display:flex;align-items:center;justify-content:center;width:100%;min-height:72px;margin:.25rem auto .5rem}.logo-wrap svg.default-logo{display:block;width:calc(100% + 1rem);height:auto;margin:-.25rem auto .1rem;max-width:320px}.logo-wrap img.custom-logo{display:block;max-width:220px;max-height:96px;width:auto;height:auto;object-fit:contain}.default-logo .wordmark{fill:var(--text)}.default-logo .symbol{fill:#db2d54}h1{font-size:1.125rem;font-weight:600;letter-spacing:-.015em;margin-bottom:.25rem}.sub{font-size:.8125rem;color:var(--muted);margin-bottom:1.75rem}.alert{display:flex;align-items:flex-start;gap:.625rem;padding:.625rem .75rem;background:color-mix(in srgb,#db2d54 10%,transparent);border:1px solid color-mix(in srgb,#db2d54 30%,transparent);border-radius:8px;font-size:.8125rem;color:#a0192f;margin-bottom:.875rem}.alert.success{background:color-mix(in srgb,#0f7a3c 10%,transparent);border-color:color-mix(in srgb,#0f7a3c 30%,transparent);color:#0f7a3c}@media(prefers-color-scheme:dark){.alert{color:#f4839a}.alert.success{color:#58c98a}}.alert svg{flex-shrink:0;margin-top:1px}.fields{display:flex;flex-direction:column;gap:.875rem}.field label{font-size:.75rem;font-weight:500;color:var(--muted);letter-spacing:.03em;display:flex;justify-content:space-between;align-items:center;margin-bottom:.3125rem}input[type=email],input[type=password],input[type=text]{width:100%;padding:.5625rem .75rem;font-size:.9375rem;font-family:inherit;background:var(--input-bg);border:1px solid var(--border);border-radius:8px;color:var(--text);outline:0;transition:border-color .15s;-webkit-appearance:none}input[type=email]:focus,input[type=password]:focus,input[type=text]:focus{border-color:color-mix(in srgb,var(--info) 60%,transparent);box-shadow:0 0 0 3px color-mix(in srgb,var(--info) 12%,transparent)}input[type=email]::placeholder,input[type=password]::placeholder,input[type=text]::placeholder{color:var(--muted);opacity:.7}#otp{letter-spacing:.2em;font-size:1.0625rem;text-align:center;font-variant-numeric:tabular-nums}#device-code{letter-spacing:.15em;font-size:1.0625rem;text-align:center;text-transform:uppercase;font-variant-numeric:tabular-nums}.btn{width:100%;margin-top:.375rem;padding:.625rem 0;font-size:.9375rem;font-weight:500;font-family:inherit;background:var(--accent);color:var(--accent-fg);border:none;border-radius:8px;cursor:pointer;letter-spacing:-.01em;transition:opacity .15s}.btn:hover{opacity:.88}.btn:active{opacity:.75;transform:scale(.99)}.btn[disabled]{opacity:.55;cursor:not-allowed;transform:none}[hidden]{display:none!important}</style> </head> <body> <div class="card"> <div class="logo-wrap" id="logo-wrap"> <svg class="default-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 680.5 252.1" aria-label="Logo"> <path class="wordmark" d="M227.8 143.6c.3 4.2 2.1 7.6 5.1 10.1 3.1 2.5 7.1 3.8 12.1 3.8 4.3 0 7.9-.9 10.5-2.8 2.7-1.9 4-4.5 4-7.8 0-2.4-.7-4.3-2.2-5.7-1.5-1.4-3.4-2.5-6-3.2-2.5-.7-6-1.5-10.6-2.3-4.6-.8-8.6-1.9-11.9-3.2-3.3-1.3-6-3.3-8.1-6.1-2.1-2.7-3.1-6.3-3.1-10.7 0-4.1 1.1-7.7 3.2-10.9s5.1-5.7 9-7.4c3.8-1.8 8.2-2.6 13.2-2.6 5.1 0 9.6 1 13.7 2.9 4 1.9 7.2 4.5 9.5 7.8s3.6 7.1 3.8 11.4h-11.5c-.4-3.7-2-6.6-4.8-8.9-2.8-2.2-6.3-3.4-10.6-3.4-4.1 0-7.5.9-9.9 2.7-2.5 1.8-3.7 4.3-3.7 7.6 0 2.3.7 4.1 2.2 5.5 1.5 1.4 3.4 2.4 5.9 3.1 2.4.7 5.9 1.4 10.5 2.2 4.6.8 8.6 1.9 11.9 3.3 3.3 1.4 6 3.4 8.2 6 2.1 2.6 3.2 6.1 3.2 10.5 0 4.2-1.1 8-3.4 11.3-2.2 3.3-5.4 5.9-9.4 7.8-4 1.9-8.6 2.8-13.7 2.8-5.6 0-10.6-1-14.9-3.1-4.3-2-7.6-4.9-10-8.5-2.4-3.6-3.7-7.8-3.7-12.5l11.5.3zM278.5 102.1l11-2.1v14.6h12.6v9.7h-12.6v27.2c0 2 .4 3.5 1.2 4.3.8.9 2.2 1.3 4.2 1.3h8.4v9.7h-10.6c-5 0-8.6-1.2-10.8-3.5-2.2-2.3-3.4-5.9-3.4-10.7v-50.5zM356.8 114.6v52.2h-9.7l-1.2-7.9c-1.8 2.6-4.2 4.7-7 6.2-2.9 1.6-6.2 2.3-10 2.3-4.8 0-9-1.1-12.7-3.2-3.7-2.1-6.7-5.2-8.8-9.3-2.1-4-3.2-8.8-3.2-14.2 0-5.3 1.1-10 3.2-14s5.1-7.2 8.8-9.4c3.7-2.2 7.9-3.3 12.6-3.3 3.9 0 7.2.7 10.1 2.2 2.9 1.5 5.2 3.5 6.9 6.1l1.3-7.6h9.7zm-15.1 38.7c2.8-3.2 4.2-7.3 4.2-12.4 0-5.2-1.4-9.4-4.2-12.6-2.8-3.3-6.5-4.9-11-4.9-4.6 0-8.2 1.6-11 4.8-2.8 3.2-4.2 7.4-4.2 12.5 0 5.2 1.4 9.4 4.2 12.6 2.8 3.2 6.5 4.8 11 4.8s8.2-1.6 11-4.8zM365.5 97.5l11-2.1v71.3h-11V97.5zM380.3 114.6h11.6l11.9 39.9 11.9-39.9h10.1l11.4 39.9 12.3-39.9h11.2l-17.3 52.2h-11.8l-11-35.5-11.4 35.5-11.9.1-17-52.3zM513.7 114.6v52.2H504l-1.2-7.9c-1.8 2.6-4.2 4.7-7 6.2-2.9 1.6-6.2 2.3-10 2.3-4.8 0-9-1.1-12.7-3.2-3.7-2.1-6.7-5.2-8.8-9.3-2.1-4-3.2-8.8-3.2-14.2 0-5.3 1.1-10 3.2-14s5.1-7.2 8.8-9.4c3.7-2.2 7.9-3.3 12.6-3.3 3.9 0 7.2.7 10.1 2.2 2.9 1.5 5.2 3.5 6.9 6.1l1.3-7.6h9.7zm-15.1 38.7c2.8-3.2 4.2-7.3 4.2-12.4 0-5.2-1.4-9.4-4.2-12.6-2.8-3.3-6.5-4.9-11-4.9-4.6 0-8.2 1.6-11 4.8-2.8 3.2-4.2 7.4-4.2 12.5 0 5.2 1.4 9.4 4.2 12.6 2.8 3.2 6.5 4.8 11 4.8 4.6 0 8.2-1.6 11-4.8zM551.3 114.6v10.3h-4.9c-4.6 0-7.8 1.5-9.9 4.4-2 3-3.1 6.7-3.1 11.3v26.2h-11v-52.2h9.8l1.2 7.8c1.5-2.4 3.4-4.4 5.8-5.8 2.4-1.4 5.6-2.1 9.6-2.1h2.5zM556.3 102.1l11-2.1v14.6h12.6v9.7h-12.6v27.2c0 2 .4 3.5 1.2 4.3.8.9 2.2 1.3 4.2 1.3h8.4v9.7h-10.6c-5 0-8.6-1.2-10.8-3.5s-3.4-5.9-3.4-10.7v-50.5z"/> <path class="symbol" d="M149.1 84.7h-4.8l-44.8 25.9v8.3l44.8 25.9h4.8l44.8-25.9v-8.3l-44.8-25.9zm32.9 30h-35.3V94.4l35.3 20.3zm-35.3 20.4-35.3-20.4 27-15.6v20.2l6.3 3.6h22.9l-20.9 12.2zM99.5 129.9v11l44.8 25.9h4.8l44.8-25.9v-11l-47.2 27.3zM187.3 166.8l6.6-3.8v-11l-25.7 14.8zM99.5 163l6.6 3.8h19.1L99.5 152z"/> </svg> </div> <h1 id="title">Sign in</h1> <p class="sub" id="subtitle">Enter your credentials to continue</p> <div class="alert" id="alert" hidden aria-live="polite"> <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="10"/> <line x1="12" y1="8" x2="12" y2="12"/> <line x1="12" y1="16" x2="12.01" y2="16"/> </svg> <span id="alert-msg"></span> </div> <form class="fields" id="login-form" novalidate> <div class="field" id="field-username"> <label for="username">Username</label> <input id="username" name="username" type="text" placeholder="you@example.com" autocomplete="username" autocapitalize="none" autocorrect="off" spellcheck="false" required> </div> <div class="field" id="field-password"> <label for="password">Password</label> <input id="password" name="password" type="password" placeholder="••••••••" autocomplete="current-password" required> </div> <div class="field" id="field-device-code" hidden> <label for="device-code">Device code</label> <input id="device-code" name="device-code" type="text" placeholder="XXXX-XXXX" autocomplete="off" autocapitalize="characters" spellcheck="false"> </div> <div class="field" id="field-otp" hidden> <label for="otp">One-time code</label> <input id="otp" name="otp" type="text" placeholder="000000" maxlength="6" inputmode="numeric" pattern="\d{6}" autocomplete="one-time-code"> </div> <button type="submit" class="btn" id="submit-btn">Sign in</button> </form> </div> <script>!function(){"use strict";var e=function(e){return document.getElementById(e)};function t(e,t){e.textContent=null==t?"":String(t)}function o(e){e&&(e.hidden=!1)}function n(e){e&&(e.hidden=!0)}function i(n){var i=e("alert");i.classList.remove("success"),t(e("alert-msg"),n),o(i)}function r(){n(e("alert"))}var c=new URL(window.location.href),a=c.searchParams,s=/\/device(\/|$)/.test(c.pathname),l={client_id:a.get("client_id")||"",redirect_uri:a.get("redirect_uri"),scope:a.get("scope"),state:a.get("state"),nonce:a.get("nonce"),code_challenge:a.get("code_challenge"),code_challenge_method:a.get("code_challenge_method")},d=a.get("login_hint");if(d&&(e("username").value=d),s){t(e("title"),"Authorize device"),t(e("subtitle"),"Sign in to approve this device."),o(e("field-device-code"));var u=a.get("code");u&&(e("device-code").value=u)}var f=!1,h=null;function v(t,o){if(s){var n={type:"authDevice",account_name:t.account_name,account_secret:t.account_secret,code:(e("device-code").value||"").trim()};return o&&(n.mfaToken=o),n}var i={type:"authCode",account_name:t.account_name,account_secret:t.account_secret,client_id:l.client_id||""};return l.redirect_uri&&(i.redirectUri=l.redirect_uri),l.scope&&(i.scope=l.scope),l.state&&(i.state=l.state),l.nonce&&(i.nonce=l.nonce),l.code_challenge&&(i.codeChallenge=l.code_challenge),l.code_challenge_method&&(i.codeChallengeMethod=l.code_challenge_method),o&&(i.mfaToken=o),i}function p(c){if(!c||"object"!=typeof c||"string"!=typeof c.type)return console.log("Malformed login response:",c),void i("Temporary server failure. If the problem persists, contact your administrator.");switch(c.type){case"authenticated":return"string"!=typeof c.client_code?(console.log("Missing client_code in response:",c),void i("Temporary server failure. If the problem persists, contact your administrator.")):void function(e){var t;try{if(!l.redirect_uri)throw new Error("missing redirect_uri");t=new URL(l.redirect_uri)}catch(e){return console.log("Invalid or missing redirect_uri:",l.redirect_uri,e),void i("Temporary server failure. If the problem persists, contact your administrator.")}t.searchParams.set("code",e),l.state&&t.searchParams.set("state",l.state),window.location.assign(t.toString())}(c.client_code);case"verified":return n(e("login-form")),t(e("title"),"Device authorized"),t(e("subtitle"),"You have successfully authorized this device. You may now close this window."),r(),a="Device verified.",(d=e("alert")).classList.add("success"),t(e("alert-msg"),a),void o(d);case"mfaRequired":return f=!0,n(e("field-username")),n(e("field-password")),s&&n(e("field-device-code")),o(e("field-otp")),t(e("title"),"Two-factor authentication"),t(e("subtitle"),"Enter the 6-digit code from your authenticator app."),r(),void setTimeout(function(){e("otp").focus()},0);case"failure":return f&&(f=!1,o(e("field-username")),o(e("field-password")),s&&o(e("field-device-code")),n(e("field-otp")),e("otp").value="",t(e("title"),s?"Authorize device":"Sign in"),t(e("subtitle"),s?"Sign in to approve this device.":"Enter your credentials to continue")),void i("Invalid username or password. Please try again.");default:console.log("Unknown login response type:",c),i("Temporary server failure. If the problem persists, contact your administrator.")}var a,d}function m(t){var c;if(t&&t.preventDefault(),r(),f){var a=(e("otp").value||"").trim();return a?h?void g(v(c=h,a)):(f=!1,o(e("field-username")),o(e("field-password")),n(e("field-otp")),void i("Session expired. Please sign in again.")):void i("Please enter your one-time code.")}var l=(e("username").value||"").trim(),d=e("password").value||"";if(l&&d){if(s)if(!(e("device-code").value||"").trim())return void i("Please enter the device code.");h=c={account_name:l,account_secret:d},g(v(c,null))}else i("Please enter your username and password.")}function g(t){var o=e("submit-btn");o.disabled=!0,fetch("/api/auth",{method:"POST",credentials:"same-origin",cache:"no-store",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(t)}).then(function(e){return 200!==e.status?e.text().then(function(t){throw console.log("Login endpoint returned HTTP",e.status,t),new Error("http "+e.status)}):e.json().catch(function(e){throw console.log("Failed to parse login response JSON:",e),e})}).then(function(e){p(e)}).catch(function(e){console.log("Login request failed:",e),i("Temporary server failure. If the problem persists, contact your administrator.")}).then(function(){o.disabled=!1})}fetch("/logo",{method:"GET",credentials:"same-origin",cache:"no-cache"}).then(function(e){return e.ok?0!==(e.headers.get("content-type")||"").toLowerCase().indexOf("image/")?null:e.blob():null}).then(function(t){if(t){var o=URL.createObjectURL(t),n=new Image;n.className="custom-logo",n.alt="Logo",n.onload=function(){for(var t=e("logo-wrap");t.firstChild;)t.removeChild(t.firstChild);t.appendChild(n)},n.onerror=function(){URL.revokeObjectURL(o)},n.src=o}}).catch(function(e){console.log("Custom logo unavailable:",e)}),e("login-form").addEventListener("submit",m),e("otp").addEventListener("input",function(){this.value=this.value.replace(/\D/g,""),6===this.value.length&&m()}),setTimeout(function(){e("username").value?e("password").value?!s||e("device-code").value||e("device-code").focus():e("password").focus():e("username").focus()},0)}()</script> </body> </html> |