diff options
author | Alexis Hovorka <[email protected]> | 2024-02-13 23:11:36 -0700 |
---|---|---|
committer | Alexis Hovorka <[email protected]> | 2024-02-13 23:11:36 -0700 |
commit | 70d3a32ae766dc15fd6c21e382068f44dbaff8b8 (patch) | |
tree | c288685745e0681dc5417fe64d7495a5aae06c36 /app/public | |
parent | af96e03db9eab2a468561d1d82a32e8a7b01da90 (diff) |
[feat] Flesh out auth flow and note store
Diffstat (limited to 'app/public')
-rw-r--r-- | app/public/index.html | 21 | ||||
-rw-r--r-- | app/public/main.js | 175 |
2 files changed, 195 insertions, 1 deletions
diff --git a/app/public/index.html b/app/public/index.html index 3c435f5..d5932a1 100644 --- a/app/public/index.html +++ b/app/public/index.html @@ -52,12 +52,31 @@ <meta name="author" content="Alexis Hovorka"> <meta name="description" content="Zettelkasten-inspired note taking web app"> - <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:400&display=swap"> + <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,400;0,700;1,400;1,700&family=Roboto:ital,wght@0,400;0,500;0,700;1,400;1,700&display=swap"> <link rel="stylesheet" href="style.css"> </head> <body> <h1>Notes</h1> + <form> + <input type="text" id="username" name="username"> + <input type="password" id="password" name="password"> + <input type="checkbox" id="keep-session" name="keepSession"> + <button id="login">Log In</button> + </form> + <br> + <input type="password" id="new-password" name="newPassword"> + <button id="change-password">Change Password</button> + <br> + <input type="text" id="new-username" name="newUsername"> + <button id="change-username">Change Username</button> + <br> + <input type="text" id="uid" name="uid"> + <button id="logout">Log Out</button> + <button id="logout-everywhere">Log Out Everywhere</button> + <div id="notes"></div> + <button id="new-note">New Note</button> + <!--script src="sock.js"></script--> <script src="main.js"></script> </body> diff --git a/app/public/main.js b/app/public/main.js index ff960c2..5dc9531 100644 --- a/app/public/main.js +++ b/app/public/main.js @@ -5,6 +5,17 @@ const $ = (s,c) => (c||document).querySelector(s); function $$(x,y,z,a){a=(z||document).querySelectorAll(x);if(typeof y=="function")[].forEach.call(a,y);return a} function m(a,b,c){c=document;b=c.createElement(b||"p");b.innerHTML=a.trim();for(a=c.createDocumentFragment();c=b.firstChild;)a.appendChild(c);return a.firstChild} +function debounce(fn, delay) { + let timeout = null; + return (...args) => { + if (timeout) clearTimeout(timeout); + timeout = setTimeout(() => { + timeout = null; + fn.apply(null, args); + }, delay||500); + } +} + //sock.init(`ws${secure?"s":""}://${location.host}/ws`); //sock.on("hello", e => { // console.log("hello", e); @@ -15,4 +26,168 @@ function m(a,b,c){c=document;b=c.createElement(b||"p");b.innerHTML=a.trim();for( // navigator.serviceWorker.register("sw.js") // .then(() => console.log("Service worker registered")); //} + +function drawNote(note) { + const el = m(`<div> + <h3>${note.id}</h3> + <textarea>${note.content}</textarea> + </div>`); + + $("textarea", el).addEventListener("input", + debounce(() => { saveNote(note.id, $("textarea", el).value); }, 500)); + + $("#notes").appendChild(el); +} + +async function getNote(id) { + const res = await fetch(`${id}`); + const note = await res.json(); + console.log(note); + drawNote(note); +} + +async function getList() { + const res = await fetch("/list"); + const list = await res.json(); + console.log(list); + + for (let i=0;i<list.length;i++) { + await getNote(list[i]); + } +} + +async function saveNote(id, content) { + const res = await fetch("/"+id, { + method: "POST", + body: JSON.stringify({id, content}), + }); + + const note = await res.json(); + console.log(note); +} + +async function newNote() { + const res = await fetch("/new", { + method: "POST", + }); + + const note = await res.json(); + console.log(note); + + drawNote(note); +} + +$("#new-note").addEventListener("click", newNote); + +async function getUserData() { + const res = await fetch(`/user`); + const user = await res.json(); + console.log(user); + if (user.uid) $("#uid").value = user.uid; +} + +async function getUserSessions() { + const res = await fetch(`/session-list`); + const sessions = await res.json(); + console.log(sessions); +} + +$("#login").addEventListener("click", async () => { + const res = await fetch("/login", { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({ + username: $("#username").value, + password: $("#password").value, + keepSession: $("#keep-session").checked, + }) + }); + + // TODO check return code + + const json = await res.json(); + console.log(json); + + // TODO "Must Change Password" flow + + getUserData(); + getList(); +}); + +$("#change-password").addEventListener("click", async () => { + const res = await fetch("/change-password", { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({ + username: $("#username").value, + password: $("#password").value, + newPassword: $("#new-password").value, + keepSession: $("#keep-session").checked, + }) + }); + + const json = await res.json(); + console.log(json); + + getUserData(); +}); + +$("#change-username").addEventListener("click", async () => { + const res = await fetch("/change-username", { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({ + newUsername: $("#new-username").value, + password: $("#password").value, + }) + }); + + const json = await res.json(); + console.log(json); + + getUserData(); +}); + +$("#logout").addEventListener("click", async () => { + const res = await fetch("/logout", { + method: "POST", + }); + + //const json = await res.json(); + //console.log(json); + console.log("Logged out"); + $("#uid").value = ""; +}); + +$("#logout-everywhere").addEventListener("click", async () => { + const res = await fetch("/deauth-all", { + method: "POST", + }); + + console.log("Logged out everywhere"); + $("#uid").value = ""; +}); + +try { getUserData(); getList(); getUserSessions(); } catch(e) {} + +// TODO make sure to save unsynced delta even if token has expired, then sync when logged in +// TODO Cookie consent on signup; "only necessary for maintaining user session and cached data" +// TODO "Review sessions" popup client-side when relogin required on a token which shouldn't have expired yet + +// TODO prevent data: and javascript: links? +// Also embedding of external content? +// encodeURI *AND* encode for attribute (quote marks etc) +// https://github.com/cure53/DOMPurify +// +// elem.textContent = dangerVariable; !!!!!!!!! +// elem.insertAdjacentText(dangerVariable); +// elem.className = dangerVariable; +// elem.setAttribute(safeName, dangerVariable); +// formfield.value = dangerVariable; +// document.createTextNode(dangerVariable); +// document.createElement(dangerVariable); +// elem.innerHTML = DOMPurify.sanitize(dangerVar); +// https://github.com/cure53/DOMPurify/blob/main/src/attrs.js +// https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html + }); |