Browse Source

Merge pull request #288 from bennett-sh/account-settings

Emrik Östling 2 months ago
parent
commit
6dc60679bb
2 changed files with 170 additions and 0 deletions
  1. 13 0
      src/components/header.tsx
  2. 157 0
      src/index.tsx

+ 13 - 0
src/components/header.tsx

@@ -30,6 +30,19 @@ export const Header = ({
             </a>
           </li>
         )}
+        {!allowUnauthenticated ? (
+          <li>
+            <a
+              class={`
+                text-accent-600 transition-all
+                hover:text-accent-500 hover:underline
+              `}
+              href={`${webroot}/account`}
+            >
+              Account
+            </a>
+          </li>
+        ) : null}
         {!allowUnauthenticated ? (
           <li>
             <a

+ 157 - 0
src/index.tsx

@@ -475,6 +475,163 @@ const app = new Elysia({
 
     return redirect(`${WEBROOT}/login`, 302);
   })
+  .get("/account", async ({ jwt, redirect, cookie: { auth } }) => {
+    if (!auth?.value) {
+      return redirect(`${WEBROOT}/`);
+    }
+    const user = await jwt.verify(auth.value);
+
+    if (!user) {
+      return redirect(`${WEBROOT}/`, 302);
+    }
+
+    const userData = db
+      .query("SELECT * FROM users WHERE id = ?")
+      .as(User)
+      .get(user.id);
+
+    if (!userData) {
+      return redirect(`${WEBROOT}/`, 302);
+    }
+
+    return (
+      <BaseHtml webroot={WEBROOT} title="ConvertX | Login">
+        <>
+          <Header
+            webroot={WEBROOT}
+            accountRegistration={ACCOUNT_REGISTRATION}
+            allowUnauthenticated={ALLOW_UNAUTHENTICATED}
+            hideHistory={HIDE_HISTORY}
+          />
+          <main
+            class={`
+              w-full px-2
+              sm:px-4
+            `}
+          >
+            <article class="article">
+              <form method="post" class="flex flex-col gap-4">
+                <fieldset class="mb-4 flex flex-col gap-4">
+                  <label class="flex flex-col gap-1">
+                    Email
+                    <input
+                      type="email"
+                      name="email"
+                      class="rounded-sm bg-neutral-800 p-3"
+                      placeholder="Email"
+                      autocomplete="email"
+                      value={userData.email}
+                      required
+                    />
+                  </label>
+                  <label class="flex flex-col gap-1">
+                    Password (leave blank for unchanged)
+                    <input
+                      type="password"
+                      name="newPassword"
+                      class="rounded-sm bg-neutral-800 p-3"
+                      placeholder="Password"
+                      autocomplete="new-password"
+                    />
+                  </label>
+                  <label class="flex flex-col gap-1">
+                    Current Password
+                    <input
+                      type="password"
+                      name="password"
+                      class="rounded-sm bg-neutral-800 p-3"
+                      placeholder="Password"
+                      autocomplete="current-password"
+                      required
+                    />
+                  </label>
+                </fieldset>
+                <div role="group">
+                  <input
+                    type="submit"
+                    value="Update"
+                    class="btn-primary w-full"
+                  />
+                </div>
+              </form>
+            </article>
+          </main>
+        </>
+      </BaseHtml>
+    );
+  })
+  .post(
+    "/account",
+    async function handler({ body, set, redirect, jwt, cookie: { auth } }) {
+      if (!auth?.value) {
+        return redirect(`${WEBROOT}/login`, 302);
+      }
+
+      const user = await jwt.verify(auth.value);
+      if (!user) {
+        return redirect(`${WEBROOT}/login`, 302);
+      }
+      const existingUser = db
+        .query("SELECT * FROM users WHERE id = ?")
+        .as(User)
+        .get(user.id);
+
+      if (!existingUser) {
+        if (auth?.value) {
+          auth.remove();
+        }
+        return redirect(`${WEBROOT}/login`, 302);
+      }
+
+      const validPassword = await Bun.password.verify(
+        body.password,
+        existingUser.password,
+      );
+
+      if (!validPassword) {
+        set.status = 403;
+        return {
+          message: "Invalid credentials.",
+        };
+      }
+
+      const fields = [];
+      const values = [];
+
+      if (body.email) {
+        const existingUser = await db
+          .query("SELECT id FROM users WHERE email = ?")
+          .as(User)
+          .get(body.email);
+        if (existingUser && existingUser.id.toString() !== user.id) {
+          set.status = 409;
+          return { message: "Email already in use." };
+        }
+        fields.push("email");
+        values.push(body.email);
+      }
+      if (body.newPassword) {
+        fields.push("password");
+        values.push(await Bun.password.hash(body.newPassword));
+      }
+
+      if (fields.length > 0) {
+        db.query(
+          `UPDATE users SET ${fields.map((field) => `${field}=?`).join(", ")} WHERE id=?`,
+        ).run(...values, user.id);
+      }
+
+      return redirect(`${WEBROOT}/`, 302);
+    },
+    {
+      body: t.Object({
+        email: t.MaybeEmpty(t.String()),
+        newPassword: t.MaybeEmpty(t.String()),
+        password: t.String(),
+      }),
+    },
+  )
+
   .get("/", async ({ jwt, redirect, cookie: { auth, jobId } }) => {
     if (!ALLOW_UNAUTHENTICATED) {
       if (FIRST_RUN) {