diff --git a/main.js b/main.js index ceadd76..7170bba 100644 --- a/main.js +++ b/main.js @@ -2,12 +2,6 @@ const { app, BrowserWindow, nativeImage } = require('electron'); const path = require('path'); -if (process.platform === 'darwin') { - setTimeout(() => { - app.dock.bounce('informational'); // or 'critical' - }, 1000); -} - function createWindow() { const win = new BrowserWindow({ show: false, // window is hidden diff --git a/package.json b/package.json index a1c51f2..ca950a4 100644 --- a/package.json +++ b/package.json @@ -7,16 +7,17 @@ }, "dependencies": { "argon2": "^0.44.0", - "chalk": "^4.1.2", + "chalk": "^4.1.2", "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^17.2.3", "express": "^4.18.2", "jsonwebtoken": "^9.0.2", "moment": "^2.30.1", - "ws": "^8.18.3" + "ws": "^8.18.3", + "zod": "^4.1.12" }, "devDependencies": { "electron": "^25.0.0" } -} \ No newline at end of file +} diff --git a/server/auth.js b/server/auth.js index de4ce97..164a18a 100644 --- a/server/auth.js +++ b/server/auth.js @@ -30,7 +30,7 @@ export default class AuthHandler { async login(req, res) { const { email, password } = req.body; - let foundUser = global.db.get.userByEmail(email) + let foundUser = global.db.members.getByEmail(email) if(!foundUser) { res.status(400).json({ error: 'Incorrect email.' }); return; diff --git a/server/db/db.js b/server/db/db.js index c78ab58..e92bf81 100644 --- a/server/db/db.js +++ b/server/db/db.js @@ -49,11 +49,7 @@ export default class Database { } } - generateUserID() { - let id = this.labels["User"].length + 1; - while (this.get.user(`user-${id}`)) { - id++; - } - return `user-${id}`; // O(1) most of the time + saveData() { + } } \ No newline at end of file diff --git a/server/db/model/Members.js b/server/db/model/Members.js index 42271bc..936cd79 100644 --- a/server/db/model/Members.js +++ b/server/db/model/Members.js @@ -1,11 +1,33 @@ import OrderedObject from "./OrderedObject.js" +const argon2 = require("argon2") +const { z } = require("zod") export default class Members extends OrderedObject { + addressSchema = z.object({ + address1: z.string(), + address2: z.string().optional(), + zip: z.string().regex(/^\d{5}(-\d{4})?$/), + state: z.string(), + city: z.string() + }) + schema = z.object({ + email: z.string().email(), + firstName: z.string(), + lastName: z.number().int().min(0), + password: z.string(), + address: this.addressSchema + }) - add(newMember) { + isHashed = (s) => {return s.startsWith("$argon2")} + + async add(newMember) { console.log("adding ", newMember) let id = `MEMBER-${newMember.email}` - if(this.validate(id, newMember)) { + if(this.schema.safeParse(newMember)) { + if(!this.isHashed(newMember.password)) { + const hash = await argon2.hash(newMember.password); + newMember.password = hash + } try { super.add(id, newMember) } catch(e) { @@ -17,48 +39,7 @@ export default class Members extends OrderedObject { } } - validate(id, node) { - let idTraits = { - firstWord: "MEMBER" - } - - let fields = [ - "firstName", - "lastName", - "email", - "password" - ] - - let checkID = () => { - let split = id.split("-") - return ( - split[0] === idTraits.firstWord - && split[1].includes("@") - && split[1].includes(".") - ) - } - let idres = checkID() - if(!idres) { - console.log("id failed: ", id) - return false - } - - let checkFields = () => { - for(let i = 0; i < fields.length; i++) { - if(!node[fields[i]]) { - throw new Error(`Member ${node.email} is missing trait ${fields[i]}`) - return false - } else { - return true - } - } - } - let fieldres = checkFields() - if(!fieldres) { - console.log("fields failed") - return false - } - - return true + getByEmail(email) { + return super.get(`MEMBER-${email}`) } } \ No newline at end of file diff --git a/server/index.js b/server/index.js index 31739c5..6f620af 100644 --- a/server/index.js +++ b/server/index.js @@ -43,11 +43,11 @@ class Server { next() } - newUserSubmission = (req, res) => { + newUserSubmission = async (req, res) => { const { token } = req.query; try { - db.members.add(req.body) - return res.redirect(`/signin/?new=true`); + await db.members.add(req.body) + return res.status(200).json({}); } catch(e) { return res.status(e.status).json({ error: 'Error adding new member' }); } diff --git a/ui/_/code/quill.js b/ui/_/code/quill.js index 5659c39..2cfdcf3 100644 --- a/ui/_/code/quill.js +++ b/ui/_/code/quill.js @@ -1,6 +1,7 @@ /* Sam Russell Captured Sun + 11.23.25 - Added onSubmit() event for form submission 11.20.25 - Added "pct" style unit, added alignVertical and alignHorizontal for flex boxes 11.19.25 - Allowing for "auto" values in otherwise numeric styles, adding vmin and vmax units 11.17.25.3 - Adding styles() and fixing dynamic function from earlier @@ -907,6 +908,13 @@ HTMLElement.prototype.onInput = function(cb) { return this; }; +HTMLElement.prototype.onSubmit = function(cb) { + if(!this.matches('form')) + throw new Error("Can't put form event on non-form element!") + this._storeListener("submit", cb); + return this; +}; + HTMLElement.prototype.onTouch = function(cb) { const onStart = () => cb.call(this, true); const onEnd = () => cb.call(this, false); diff --git a/ui/public/components/SignupForm.js b/ui/public/components/SignupForm.js index 73904bf..63d96f1 100644 --- a/ui/public/components/SignupForm.js +++ b/ui/public/components/SignupForm.js @@ -1,5 +1,8 @@ class SignupForm extends Shadow { + errorMessage = "Error signing up. Please try again later or email info@hyperia.so if the problem persists." + successMessage = "Success! You may now log in." + inputStyles(el) { return el .border("1px solid var(--accent)") @@ -11,6 +14,13 @@ class SignupForm extends Shadow { VStack(() => { + p() + .attr({id: "signupMessage"}) + .display("none") + .padding(1, em) + .color("var(--main)") + .background("var(--accent)") + HStack(() => { VStack(() => { @@ -71,10 +81,51 @@ class SignupForm extends Shadow { button("Submit") }) .gap(2, em) - - console.log(window.location.pathname) }) - .attr({action: window.location.pathname + window.location.search, method: "POST"}) + .onSubmit(async (e) => { + e.preventDefault() + console.log("submitting") + $("#signupMessage").style.display = "none" + + const formData = new FormData(this.$("form")); + const data = Object.fromEntries(formData.entries()); + let newMember = { + "email": data.email, + "firstName": data.firstName, + "lastName": data.lastName, + "password": data.password + } + let address = { + "address1": data.address1, + "address2": data.address2, + "zip": data.zip, + "state": data.state, + "city": data.city + } + newMember.address = address + + try { + const response = await fetch(window.location.pathname + window.location.search, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(newMember) + }); + + if (!response.ok) { + $("#signupMessage").style.display = "block" + $("#signupMessage").innerText = this.errorMessage + throw new Error(`HTTP error! status: ${response.status}`); + } else { + $("#signupMessage").style.display = "block" + $("#signupMessage").innerText = this.successMessage + } + + } catch (err) { + console.error("Fetch error:", err); + } + }) .x(50, vw).y(53, vh) .width(60, vw) .center()