diff options
author | gumi <git@gumi.ca> | 2019-06-29 11:21:40 -0400 |
---|---|---|
committer | gumi <git@gumi.ca> | 2019-06-29 11:21:40 -0400 |
commit | c650856bddeac08ce4bf4f1cbe9f67ab0ed91890 (patch) | |
tree | 3558031f9c7810fa9d4769eef1bc2f142a4e6f51 /src | |
parent | 7f3f119aefedce748de125124999e8106f600e92 (diff) | |
download | landing-c650856bddeac08ce4bf4f1cbe9f67ab0ed91890.tar.gz landing-c650856bddeac08ce4bf4f1cbe9f67ab0ed91890.tar.bz2 landing-c650856bddeac08ce4bf4f1cbe9f67ab0ed91890.tar.xz landing-c650856bddeac08ce4bf4f1cbe9f67ab0ed91890.zip |
rework the password reset process: api v2.2
Diffstat (limited to 'src')
-rw-r--r-- | src/reset.html | 224 | ||||
-rw-r--r-- | src/reset_password.html | 356 | ||||
-rw-r--r-- | src/reset_password_init.html | 306 | ||||
-rw-r--r-- | src/reset_user.html | 302 |
4 files changed, 997 insertions, 191 deletions
diff --git a/src/reset.html b/src/reset.html index 8e334e0..5200b93 100644 --- a/src/reset.html +++ b/src/reset.html @@ -24,7 +24,7 @@ font-size: 12px; line-height: 30px; color: #777; - background: #4CAF50; + background: rgba(0, 0, 0, 0.6); } .container { @@ -34,11 +34,14 @@ position: relative; } - .container > form input[type="text"], - .container > form input[type="password"], - .container > form input[type="email"], - .container > form button { + .container > form .button { font: 400 12px/16px "Roboto", Helvetica, Arial, sans-serif; + -webkit-appearance: button; + -moz-appearance: button; + appearance: button; + display: block; + text-align: center; + text-decoration: none; } .container > form { @@ -62,14 +65,6 @@ font-weight: 400; } - .container .status { - margin: 15px 0 15px; - display: block; - font-size: 13px; - font-weight: 400; - display: none; - } - fieldset { border: medium none !important; margin: 0 0 10px; @@ -78,44 +73,25 @@ width: 100%; } - .container > form input[type="text"], - .container > form input[type="password"], - .container > form input[type="email"] { - width: 100%; - border: 1px solid #ccc; - background: #FFF; - margin: 0 0 5px; - padding: 10px; - } - - .container > form input[type="text"]:hover, - .container > form input[type="password"]:hover, - .container > form input[type="email"]:hover { - -webkit-transition: border-color 0.3s ease-in-out; - -moz-transition: border-color 0.3s ease-in-out; - transition: border-color 0.3s ease-in-out; - border: 1px solid #aaa; - } - - .container > form button { + .container > form .button { cursor: pointer; width: 100%; border: none; - background: #4CAF50; + background: rgba(0, 0, 0, 0.6); color: #FFF; margin: 0 0 5px; padding: 10px; font-size: 15px; } - .container > form button:hover { - background: #43A047; + .container > form .button:hover { + background: rgba(0, 0, 0, 0.9); -webkit-transition: background 0.3s ease-in-out; -moz-transition: background 0.3s ease-in-out; transition: background-color 0.3s ease-in-out; } - .container > form button:active { + .container > form .button:active { box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5); } @@ -124,20 +100,6 @@ border: 1px solid #aaa; } - .grecaptcha-badge { - margin-bottom: 2em; - margin-top: 2em; - } - - .container > form.error .status { - display: block!important; - } - - input.invalid { - border-color: #900!important; - background-color: #FDD!important; - } - .warning { color: #fff; margin: 1em; @@ -146,30 +108,7 @@ padding: 1em; border: 3px yellow dashed; } - - ::-webkit-input-placeholder { - color: #888; - } - - :-moz-placeholder { - color: #888; - } - - ::-moz-placeholder { - color: #888; - } - - :-ms-input-placeholder { - color: #888; - } </style> - <script type="text/javascript"> - /* this block is intentionally left in <head>, for legacy browsers */ - if (!("fetch" in window && "Request" in window && "setCustomValidity" in document.createElement("input"))) { - document.body.innerHTML = "<h1 style=\"color:#fff;margin:1em\">Please update your browser to continue.</h1>"; - throw new Error("outdated browser"); - } - </script> </head> <body> <noscript> @@ -181,133 +120,36 @@ </noscript> <div class="container"> - <form id="pwreset" action="#" method="POST"> + <form> <h3>The Mana World</h3> - <h4>Password reset</h4> - <span class="status">...</span> + <h4>Account recovery: please select</h4> + <br> + <fieldset> + <div> + <a class="button" href="/recover/password">I forgot my password</a> + </div> + </fieldset> <fieldset> - <input name="username" placeholder="Username" type="text" tabindex="1" minlength="4" maxlength="23" pattern="^[a-zA-Z0-9]{4,23}$" title="4-23 characters, alphanumeric" required autofocus> + <div> + <a class="button" href="/recover/username">I forgot my user name</a> + </div> </fieldset> <fieldset> - <input name="email" placeholder="Email Address (optional)" type="email" maxlength="39" tabindex="3"> + <div> + <a class="button" href="https://forums.themanaworld.org/viewtopic.php?f=20&t=7559">My account is banned</a> + </div> </fieldset> <fieldset> <div> - <button - class="g-recaptcha" - data-sitekey="6LdaVUcUAAAAAJ-7cORTu4cZCPSNjDqjz3y4nLVR" - data-callback="onSubmit" - data-badge="inline" - data-submit="...Sending" - type="submit">Submit</button> + <a class="button" href="https://forums.themanaworld.org/viewtopic.php?f=20&t=6472">My account was compromised</a> </div> </fieldset> + <fieldset> + <div> + <a class="button" href="https://forums.themanaworld.org/viewforum.php?f=3">I need help with something else</a> + </div> + </fieldset> </form> </div> - <script> - const nodes = { - uname: document.querySelector("input[name=username]"), - pwd: document.querySelector("input[name=password]"), - email: document.querySelector("input[name=email]"), - code: document.querySelector("input[name=code]"), - form: document.querySelector(".container > form"), - status: document.querySelector(".status"), - captcha: undefined - }; - - const validateInput = node => { - node.setCustomValidity(""); - if (node.checkValidity() === false) { - node.classList.add("invalid"); - node.setCustomValidity(node.title); - node.focus(); - return false; - } else { - node.classList.remove("invalid"); - return true; - } - }; - - nodes.form.addEventListener("input", event => { - validateInput(event.target); - }); - - function onSubmit(token) { - if (nodes.form.checkValidity() === false) { - validateInput(nodes.uname) && - validateInput(nodes.pwd) && - validateInput(nodes.email); - grecaptcha.reset(); - return; - } - - if (nodes.pwd.value.length < 12 && !window.confirm("Your password is quite short. For better security you should consider using a password at least 12 characters long.\n\nDo you really want to use a weak password?")) { - grecaptcha.reset(); - nodes.pwd.focus(); - return; - } - - if (nodes.email.value.length < 3 && !window.confirm("Warning: if you do not supply an email address you will be completely unable to reset your password if you loose it.\n\nDo you really want to continue without an email address?")) { - grecaptcha.reset(); - nodes.email.focus(); - return; - } - - const req = new Request("/api/account", { - method: "POST", - mode: "same-origin", - cache: "no-cache", - redirect: "follow", - referrer: "no-referrer", - headers: { - "Accept": "application/json", - "Content-Type": "application/json", - "X-CAPTCHA-TOKEN": token - }, - body: JSON.stringify({ - username: nodes.uname.value, - password: nodes.pwd.value, - email: nodes.email.value, - }), - }); - - fetch(req) - .then(response => { - switch(response.status) { - case 201: - return response.json(); - case 400: - throw new Error("Malformed request!"); - case 403: - throw new Error("Failed the captcha challenge!"); - case 409: - nodes.uname.focus(); - throw new Error("This username already exists!"); - case 429: - throw new Error("Too many requests. Please try again later."); - case 500: - throw new Error("Internal server error. Please try again later."); - case 502: - throw new Error("Couldn't reach the server. Please try again later."); - default: - throw new Error(`Received status code ${response.status}.`); - } - }) - .then(response => { - nodes.form.innerText = "Account created successfully.\nIt may take a few minutes before you are able to log in."; - }) - .catch(error => { - nodes.form.classList.add("error"); - nodes.status.innerText = error; - nodes.status.style.display = "block"; // <= MS Edge bug - grecaptcha.reset(); - }); - } - - function Init() { - nodes.captcha = document.querySelector(".grecaptcha-badge"); - } - </script> - <script src="https://www.google.com/recaptcha/api.js?onload=Init" async defer></script> </body> </html> diff --git a/src/reset_password.html b/src/reset_password.html new file mode 100644 index 0000000..9c04907 --- /dev/null +++ b/src/reset_password.html @@ -0,0 +1,356 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <title>The Mana World</title> + <style> + * { + margin: 0; + padding: 0; + box-sizing: border-box; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + -o-font-smoothing: antialiased; + font-smoothing: antialiased; + text-rendering: optimizeLegibility; + } + + body { + font-family: "Roboto", Helvetica, Arial, sans-serif; + font-weight: 100; + font-size: 12px; + line-height: 30px; + color: #777; + background: rgb(199, 83, 83); + } + + .container { + max-width: 400px; + width: 100%; + margin: 0 auto; + position: relative; + } + + .container > form input[type="text"], + .container > form input[type="password"], + .container > form input[type="email"], + .container > form button { + font: 400 12px/16px "Roboto", Helvetica, Arial, sans-serif; + } + + .container > form { + background: #F9F9F9; + padding: 25px; + margin: 150px 0; + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24); + } + + .container > form h3 { + display: block; + font-size: 30px; + font-weight: 300; + margin-bottom: 10px; + } + + .container > form h4 { + margin: 5px 0 15px; + display: block; + font-size: 13px; + font-weight: 400; + } + + .container .status { + margin: 15px 0 15px; + display: block; + font-size: 13px; + font-weight: 400; + display: none; + } + + fieldset { + border: medium none !important; + margin: 0 0 10px; + min-width: 100%; + padding: 0; + width: 100%; + } + + .container > form input[type="text"], + .container > form input[type="password"], + .container > form input[type="email"] { + width: 100%; + border: 1px solid #ccc; + background: #FFF; + margin: 0 0 5px; + padding: 10px; + } + + .container > form input[type="text"]:hover, + .container > form input[type="password"]:hover, + .container > form input[type="email"]:hover { + -webkit-transition: border-color 0.3s ease-in-out; + -moz-transition: border-color 0.3s ease-in-out; + transition: border-color 0.3s ease-in-out; + border: 1px solid #aaa; + } + + .container > form button { + cursor: pointer; + width: 100%; + border: none; + background: rgba(0, 0, 0, 0.6); + color: #FFF; + margin: 0 0 5px; + padding: 10px; + font-size: 15px; + } + + .container > form button:hover { + background: rgba(0, 0, 0, 0.9); + -webkit-transition: background 0.3s ease-in-out; + -moz-transition: background 0.3s ease-in-out; + transition: background-color 0.3s ease-in-out; + } + + .container > form button:active { + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5); + } + + .container > form input:focus { + outline: 0; + border: 1px solid #aaa; + } + + .container > form.error .status { + display: block!important; + } + + input.invalid { + border-color: #900!important; + background-color: #FDD!important; + } + + /*input:focus:required:valid { + background-color: greenyellow!important; + }*/ + + .warning { + color: #fff; + margin: 1em; + display: inline-block; + background: orangered; + padding: 1em; + border: 3px yellow dashed; + } + + ::-webkit-input-placeholder { + color: #888; + } + + :-moz-placeholder { + color: #888; + } + + ::-moz-placeholder { + color: #888; + } + + :-ms-input-placeholder { + color: #888; + } + </style> + <script type="text/javascript"> + /* this block is intentionally left in <head>, for legacy browsers */ + if (!("fetch" in window && "Request" in window && "setCustomValidity" in document.createElement("input"))) { + document.body.innerHTML = "<h1 style=\"color:#fff;margin:1em\">Please update your browser to continue.</h1>"; + throw new Error("outdated browser"); + } + </script> + </head> + <body> + <noscript> + <div class="warning"> + <h1>Javascript is required for this page.</h1> + <!-- link to license --> + <!-- link to source code --> + </div> + </noscript> + + <div class="container"> + <form action="#" method="POST"> + <h3>The Mana World</h3> + <h4>Password reset</h4> + <span class="status">...</span> + <fieldset> + <input name="username" placeholder="Username" type="text" tabindex="1" minlength="4" maxlength="23" pattern="^[a-zA-Z0-9]{4,23}$" title="4-23 characters, alphanumeric" required autofocus> + </fieldset> + <fieldset> + <input name="password" placeholder="New Password" type="password" tabindex="2" minlength="6" maxlength="23" pattern="^[a-zA-Z0-9]{6,23}$" title="6-23 characters, alphanumeric" required> + </fieldset> + <fieldset> + <div class="g-recaptcha" + data-sitekey="6LdaVUcUAAAAAJ-7cORTu4cZCPSNjDqjz3y4nLVR" + data-callback="onCaptchaCompletion" + data-size="invisible"> + </div> + <div> + <button type="submit">Submit</button> + </div> + </fieldset> + </form> + </div> + <script> + const uuid = document.location.hash.slice(1); + var recaptcha_loaded = false; + + const nodes = { + uname: document.querySelector("input[name=username]"), + pwd: document.querySelector("input[name=password]"), + form: document.querySelector(".container > form"), + status: document.querySelector(".status"), + button: document.querySelector(".container > form button"), + }; + + if (uuid.length < 6) { + nodes.form.innerText = "The verification code is missing from the URL.\nCopy-paste the full URL provided in the email and try again."; + } + + const validateInput = node => { + node.setCustomValidity(""); + if (node.checkValidity() === false && node.value != "") { + node.classList.add("invalid"); + return false; + } else { + node.classList.remove("invalid"); + return true; + } + }; + + nodes.form.addEventListener("input", event => { + validateInput(event.target); + }); + + nodes.pwd.addEventListener("change", e => { + if (e.isTrusted && nodes.form.querySelector("input") && Reflect.has(window, "Rusha") && nodes.pwd.checkValidity()) { + const full_hash = Rusha.createHash().update(nodes.pwd.value).digest("hex"); + const hash_prefix = full_hash.substring(0, 5); + const hash_suffix = full_hash.substring(5); + + const req = new Request(`https://api.pwnedpasswords.com/range/${hash_prefix}`, { + method: "GET", + mode: "cors", + cache: "force-cache", + referrer: "no-referrer", + }); + + fetch(req) + .then(response => response.text()) + .then(response => { + const found = response.split("\n").some(h => { + const [hs, times] = h.split(":"); + + if (hash_suffix.toUpperCase() === hs.toUpperCase()) + return true; + + return false; + }); + + if (found === true) { + nodes.form.classList.add("error"); + nodes.status.innerText = "WARNING: This password has previously appeared in a data breach. Please use a more secure alternative.\n>> checked by haveibeenpwned.com\n\n"; + nodes.status.style.display = "block"; // <= MS Edge bug + nodes.pwd.focus(); + nodes.pwd.classList.add("invalid"); + } else { + nodes.form.classList.remove("error"); + nodes.status.style.display = "none"; + } + }); + } + }); + + nodes.form.addEventListener("submit", e => { + e.preventDefault(); + e.stopPropagation(); + + if (nodes.pwd.value.length < 12 && !window.confirm("Your password is quite short. For better security you should consider using a password at least 12 characters long.\n\nDo you really want to use a weak password?")) { + nodes.pwd.focus(); + return; + } + + if (!recaptcha_loaded || !Reflect.has(window, "grecaptcha")) { + nodes.form.classList.add("error"); + nodes.status.innerText = "reCAPTCHA couldn't be loaded. Make sure to whitelist this page in noscript, ghostery or any other ad/tracker blocker."; + nodes.status.style.display = "block"; // <= MS Edge bug + return; + } + + grecaptcha.execute(); + }); + + function onCaptchaCompletion(token) { + const req = new Request("/api/tmwa/account", { + method: "PUT", + mode: "same-origin", + cache: "no-cache", + redirect: "follow", + referrer: "no-referrer", + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + "X-CAPTCHA-TOKEN": token + }, + body: JSON.stringify({ + username: nodes.uname.value, + password: nodes.pwd.value, + code: uuid, + }), + }); + + fetch(req) + .then(response => { + switch(response.status) { + case 200: + case 201: + return response.json(); + case 400: + throw new Error("Malformed request!"); + case 403: + throw new Error("Failed the captcha challenge!"); + case 404: + nodes.uname.focus(); + throw new Error("No accounts found for this email address!"); + case 408: + throw new Error("Request expired. Please try again later."); + case 429: + throw new Error("Too many requests. Please try again later."); + case 500: + throw new Error("Internal server error. Please try again later."); + case 502: + throw new Error("Couldn't reach the server. Please try again later."); + default: + throw new Error(`Received status code ${response.status}.`); + } + }) + .then(response => { + nodes.form.innerText = "Your password has successfully been reset.\nHave fun playing The Mana World!"; + }) + .catch(error => { + nodes.form.classList.add("error"); + nodes.status.innerText = error; + nodes.status.style.display = "block"; // <= MS Edge bug + grecaptcha.reset(); + }); + } + + function ReInit() { + recaptcha_loaded = true; + } + </script> + <script src="https://www.google.com/recaptcha/api.js?onload=ReInit" async defer></script> + <script src="/rusha.min.js"></script> + </body> +</html> diff --git a/src/reset_password_init.html b/src/reset_password_init.html new file mode 100644 index 0000000..015f8e9 --- /dev/null +++ b/src/reset_password_init.html @@ -0,0 +1,306 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <title>The Mana World</title> + <style> + * { + margin: 0; + padding: 0; + box-sizing: border-box; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + -o-font-smoothing: antialiased; + font-smoothing: antialiased; + text-rendering: optimizeLegibility; + } + + body { + font-family: "Roboto", Helvetica, Arial, sans-serif; + font-weight: 100; + font-size: 12px; + line-height: 30px; + color: #777; + background: rgba(0, 0, 0, 0.6); + } + + .container { + max-width: 400px; + width: 100%; + margin: 0 auto; + position: relative; + } + + .container > form input[type="text"], + .container > form input[type="password"], + .container > form input[type="email"], + .container > form button { + font: 400 12px/16px "Roboto", Helvetica, Arial, sans-serif; + } + + .container > form { + background: #F9F9F9; + padding: 25px; + margin: 150px 0; + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24); + } + + .container > form h3 { + display: block; + font-size: 30px; + font-weight: 300; + margin-bottom: 10px; + } + + .container > form h4 { + margin: 5px 0 15px; + display: block; + font-size: 13px; + font-weight: 400; + } + + .container .status { + margin: 15px 0 15px; + display: block; + font-size: 13px; + font-weight: 400; + display: none; + } + + fieldset { + border: medium none !important; + margin: 0 0 10px; + min-width: 100%; + padding: 0; + width: 100%; + } + + .container > form input[type="text"], + .container > form input[type="password"], + .container > form input[type="email"] { + width: 100%; + border: 1px solid #ccc; + background: #FFF; + margin: 0 0 5px; + padding: 10px; + } + + .container > form input[type="text"]:hover, + .container > form input[type="password"]:hover, + .container > form input[type="email"]:hover { + -webkit-transition: border-color 0.3s ease-in-out; + -moz-transition: border-color 0.3s ease-in-out; + transition: border-color 0.3s ease-in-out; + border: 1px solid #aaa; + } + + .container > form button { + cursor: pointer; + width: 100%; + border: none; + background: rgba(0, 0, 0, 0.6); + color: #FFF; + margin: 0 0 5px; + padding: 10px; + font-size: 15px; + } + + .container > form button:hover { + background: rgba(0, 0, 0, 0.9); + -webkit-transition: background 0.3s ease-in-out; + -moz-transition: background 0.3s ease-in-out; + transition: background-color 0.3s ease-in-out; + } + + .container > form button:active { + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5); + } + + .container > form input:focus { + outline: 0; + border: 1px solid #aaa; + } + + .container > form.error .status { + display: block!important; + } + + input.invalid { + border-color: #900!important; + background-color: #FDD!important; + } + + /*input:focus:required:valid { + background-color: greenyellow!important; + }*/ + + .warning { + color: #fff; + margin: 1em; + display: inline-block; + background: orangered; + padding: 1em; + border: 3px yellow dashed; + } + + ::-webkit-input-placeholder { + color: #888; + } + + :-moz-placeholder { + color: #888; + } + + ::-moz-placeholder { + color: #888; + } + + :-ms-input-placeholder { + color: #888; + } + </style> + <script type="text/javascript"> + /* this block is intentionally left in <head>, for legacy browsers */ + if (!("fetch" in window && "Request" in window && "setCustomValidity" in document.createElement("input"))) { + document.body.innerHTML = "<h1 style=\"color:#fff;margin:1em\">Please update your browser to continue.</h1>"; + throw new Error("outdated browser"); + } + + if (document.location.hash.length > 6) { + document.location.pathname = "/recover/reset"; + } + </script> + </head> + <body> + <noscript> + <div class="warning"> + <h1>Javascript is required for this page.</h1> + <!-- link to license --> + <!-- link to source code --> + </div> + </noscript> + + <div class="container"> + <form action="#" method="POST"> + <h3>The Mana World</h3> + <h4>Password reset</h4> + <span class="status">...</span> + <fieldset> + <input name="email" placeholder="Email address" type="email" tabindex="1" minlength="1" maxlength="39" required autofocus> + </fieldset> + <fieldset> + <div class="g-recaptcha" + data-sitekey="6LdaVUcUAAAAAJ-7cORTu4cZCPSNjDqjz3y4nLVR" + data-callback="onCaptchaCompletion" + data-size="invisible"> + </div> + <div> + <button type="submit">Submit</button> + </div> + </fieldset> + </form> + </div> + <script> + var recaptcha_loaded = false; + + const nodes = { + email: document.querySelector("input[name=email]"), + form: document.querySelector(".container > form"), + status: document.querySelector(".status"), + button: document.querySelector(".container > form button"), + }; + + const validateInput = node => { + node.setCustomValidity(""); + if (node.checkValidity() === false && node.value != "") { + node.classList.add("invalid"); + return false; + } else { + node.classList.remove("invalid"); + return true; + } + }; + + nodes.form.addEventListener("input", event => { + validateInput(event.target); + }); + + nodes.form.addEventListener("submit", e => { + e.preventDefault(); + e.stopPropagation(); + + + + if (!recaptcha_loaded || !Reflect.has(window, "grecaptcha")) { + nodes.form.classList.add("error"); + nodes.status.innerText = "reCAPTCHA couldn't be loaded. Make sure to whitelist this page in noscript, ghostery or any other ad/tracker blocker."; + nodes.status.style.display = "block"; // <= MS Edge bug + return; + } + + grecaptcha.execute(); + }); + + function onCaptchaCompletion(token) { + const req = new Request("/api/tmwa/account", { + method: "PUT", + mode: "same-origin", + cache: "no-cache", + redirect: "follow", + referrer: "no-referrer", + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + "X-CAPTCHA-TOKEN": token + }, + body: JSON.stringify({ + email: nodes.email.value, + }), + }); + + fetch(req) + .then(response => { + switch(response.status) { + case 200: + case 201: + return response.json(); + case 400: + throw new Error("Malformed request!"); + case 403: + throw new Error("Failed the captcha challenge!"); + case 404: + nodes.uname.focus(); + throw new Error("No accounts found for this email address!"); + case 408: + throw new Error("Request expired. Please try again later."); + case 429: + throw new Error("A password reset request is already pending for this email address."); + case 500: + throw new Error("Internal server error. Please try again later."); + case 502: + throw new Error("Couldn't reach the server. Please try again later."); + default: + throw new Error(`Received status code ${response.status}.`); + } + }) + .then(response => { + nodes.form.innerText = "An email was sent with the reset instructions.\nPlease use the embedded link to proceed."; + }) + .catch(error => { + nodes.form.classList.add("error"); + nodes.status.innerText = error; + nodes.status.style.display = "block"; // <= MS Edge bug + grecaptcha.reset(); + }); + } + + function ReInit() { + recaptcha_loaded = true; + } + </script> + <script src="https://www.google.com/recaptcha/api.js?onload=ReInit" async defer></script> + </body> +</html> diff --git a/src/reset_user.html b/src/reset_user.html new file mode 100644 index 0000000..58bb417 --- /dev/null +++ b/src/reset_user.html @@ -0,0 +1,302 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <title>The Mana World</title> + <style> + * { + margin: 0; + padding: 0; + box-sizing: border-box; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + -o-font-smoothing: antialiased; + font-smoothing: antialiased; + text-rendering: optimizeLegibility; + } + + body { + font-family: "Roboto", Helvetica, Arial, sans-serif; + font-weight: 100; + font-size: 12px; + line-height: 30px; + color: #777; + background: rgba(0, 0, 0, 0.6); + } + + .container { + max-width: 400px; + width: 100%; + margin: 0 auto; + position: relative; + } + + .container > form input[type="text"], + .container > form input[type="password"], + .container > form input[type="email"], + .container > form button { + font: 400 12px/16px "Roboto", Helvetica, Arial, sans-serif; + } + + .container > form { + background: #F9F9F9; + padding: 25px; + margin: 150px 0; + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24); + } + + .container > form h3 { + display: block; + font-size: 30px; + font-weight: 300; + margin-bottom: 10px; + } + + .container > form h4 { + margin: 5px 0 15px; + display: block; + font-size: 13px; + font-weight: 400; + } + + .container .status { + margin: 15px 0 15px; + display: block; + font-size: 13px; + font-weight: 400; + display: none; + } + + fieldset { + border: medium none !important; + margin: 0 0 10px; + min-width: 100%; + padding: 0; + width: 100%; + } + + .container > form input[type="text"], + .container > form input[type="password"], + .container > form input[type="email"] { + width: 100%; + border: 1px solid #ccc; + background: #FFF; + margin: 0 0 5px; + padding: 10px; + } + + .container > form input[type="text"]:hover, + .container > form input[type="password"]:hover, + .container > form input[type="email"]:hover { + -webkit-transition: border-color 0.3s ease-in-out; + -moz-transition: border-color 0.3s ease-in-out; + transition: border-color 0.3s ease-in-out; + border: 1px solid #aaa; + } + + .container > form button { + cursor: pointer; + width: 100%; + border: none; + background: rgba(0, 0, 0, 0.6); + color: #FFF; + margin: 0 0 5px; + padding: 10px; + font-size: 15px; + } + + .container > form button:hover { + background: rgba(0, 0, 0, 0.9); + -webkit-transition: background 0.3s ease-in-out; + -moz-transition: background 0.3s ease-in-out; + transition: background-color 0.3s ease-in-out; + } + + .container > form button:active { + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5); + } + + .container > form input:focus { + outline: 0; + border: 1px solid #aaa; + } + + .container > form.error .status { + display: block!important; + } + + input.invalid { + border-color: #900!important; + background-color: #FDD!important; + } + + /*input:focus:required:valid { + background-color: greenyellow!important; + }*/ + + .warning { + color: #fff; + margin: 1em; + display: inline-block; + background: orangered; + padding: 1em; + border: 3px yellow dashed; + } + + ::-webkit-input-placeholder { + color: #888; + } + + :-moz-placeholder { + color: #888; + } + + ::-moz-placeholder { + color: #888; + } + + :-ms-input-placeholder { + color: #888; + } + </style> + <script type="text/javascript"> + /* this block is intentionally left in <head>, for legacy browsers */ + if (!("fetch" in window && "Request" in window && "setCustomValidity" in document.createElement("input"))) { + document.body.innerHTML = "<h1 style=\"color:#fff;margin:1em\">Please update your browser to continue.</h1>"; + throw new Error("outdated browser"); + } + </script> + </head> + <body> + <noscript> + <div class="warning"> + <h1>Javascript is required for this page.</h1> + <!-- link to license --> + <!-- link to source code --> + </div> + </noscript> + + <div class="container"> + <form action="#" method="POST"> + <h3>The Mana World</h3> + <h4>Account recovery</h4> + <span class="status">...</span> + <fieldset> + <input name="email" placeholder="Email address" type="email" tabindex="1" minlength="1" maxlength="39" required autofocus> + </fieldset> + <fieldset> + <div class="g-recaptcha" + data-sitekey="6LdaVUcUAAAAAJ-7cORTu4cZCPSNjDqjz3y4nLVR" + data-callback="onCaptchaCompletion" + data-size="invisible"> + </div> + <div> + <button type="submit">Submit</button> + </div> + </fieldset> + </form> + </div> + <script> + var recaptcha_loaded = false; + + const nodes = { + email: document.querySelector("input[name=email]"), + form: document.querySelector(".container > form"), + status: document.querySelector(".status"), + button: document.querySelector(".container > form button"), + }; + + const validateInput = node => { + node.setCustomValidity(""); + if (node.checkValidity() === false && node.value != "") { + node.classList.add("invalid"); + return false; + } else { + node.classList.remove("invalid"); + return true; + } + }; + + nodes.form.addEventListener("input", event => { + validateInput(event.target); + }); + + nodes.form.addEventListener("submit", e => { + e.preventDefault(); + e.stopPropagation(); + + + + if (!recaptcha_loaded || !Reflect.has(window, "grecaptcha")) { + nodes.form.classList.add("error"); + nodes.status.innerText = "reCAPTCHA couldn't be loaded. Make sure to whitelist this page in noscript, ghostery or any other ad/tracker blocker."; + nodes.status.style.display = "block"; // <= MS Edge bug + return; + } + + grecaptcha.execute(); + }); + + function onCaptchaCompletion(token) { + const req = new Request("/api/tmwa/account", { + method: "PUT", + mode: "same-origin", + cache: "no-cache", + redirect: "follow", + referrer: "no-referrer", + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + "X-CAPTCHA-TOKEN": token + }, + body: JSON.stringify({ + email: nodes.email.value, + }), + }); + + fetch(req) + .then(response => { + switch(response.status) { + case 200: + case 201: + return response.json(); + case 400: + throw new Error("Malformed request!"); + case 403: + throw new Error("Failed the captcha challenge!"); + case 404: + nodes.uname.focus(); + throw new Error("No accounts found for this email address!"); + case 408: + throw new Error("Request expired. Please try again later."); + case 429: + throw new Error("Too many requests. Please try again later."); + case 500: + throw new Error("Internal server error. Please try again later."); + case 502: + throw new Error("Couldn't reach the server. Please try again later."); + default: + throw new Error(`Received status code ${response.status}.`); + } + }) + .then(response => { + nodes.form.innerText = "An email was sent with the list of your accounts.\nYou may perform a password reset with the provided link."; + }) + .catch(error => { + nodes.form.classList.add("error"); + nodes.status.innerText = error; + nodes.status.style.display = "block"; // <= MS Edge bug + grecaptcha.reset(); + }); + } + + function ReInit() { + recaptcha_loaded = true; + } + </script> + <script src="https://www.google.com/recaptcha/api.js?onload=ReInit" async defer></script> + </body> +</html> |