Add new user
++ Create additional users for this ConvertX instance. Admins can create other + admins or normal users. +
+From c37def317ecab2bf3ea20cbdd990a3ba94fa2d25 Mon Sep 17 00:00:00 2001
From: Kosztyk <36381705+Kosztyk@users.noreply.github.com>
Date: Mon, 12 Jan 2026 12:12:42 +0200
Subject: [PATCH 01/18] added user management
added user management
---
src/pages/root.tsx | 30 ++-
src/pages/user.tsx | 561 +++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 574 insertions(+), 17 deletions(-)
diff --git a/src/pages/root.tsx b/src/pages/root.tsx
index f2a83dec..61347226 100644
--- a/src/pages/root.tsx
+++ b/src/pages/root.tsx
@@ -30,18 +30,22 @@ export const root = new Elysia().use(userService).get(
}
// validate jwt
- let user: ({ id: string } & JWTPayloadSpec) | false = false;
+ let user: ({ id: string; role: string } & JWTPayloadSpec) | false = false;
+
if (ALLOW_UNAUTHENTICATED) {
const newUserId = String(
UNAUTHENTICATED_USER_SHARING
? 0
: randomInt(2 ** 24, Math.min(2 ** 48 + 2 ** 24 - 1, Number.MAX_SAFE_INTEGER)),
);
+
const accessToken = await jwt.sign({
id: newUserId,
+ role: "user",
});
- user = { id: newUserId };
+ user = { id: newUserId, role: "user" };
+
if (!auth) {
return {
message: "No auth cookie, perhaps your browser is blocking cookies.",
@@ -57,7 +61,24 @@ export const root = new Elysia().use(userService).get(
sameSite: "strict",
});
} else if (auth?.value) {
- user = await jwt.verify(auth.value);
+ const verified = await jwt.verify(auth.value);
+
+ // If verification fails, keep user as false
+ if (verified === false) {
+ user = false;
+ } else {
+ // Ensure role is present (defensive, for older tokens)
+ const verifiedUser = verified as { id?: string; role?: string } & JWTPayloadSpec;
+
+ if (!verifiedUser.id) {
+ user = false;
+ } else if (!verifiedUser.role) {
+ // fallback: treat as normal user if role missing
+ user = { ...verifiedUser, id: verifiedUser.id, role: "user" };
+ } else {
+ user = verifiedUser as ({ id: string; role: string } & JWTPayloadSpec);
+ }
+ }
if (
user !== false &&
@@ -73,6 +94,9 @@ export const root = new Elysia().use(userService).get(
}
return redirect(`${WEBROOT}/login`, 302);
}
+
+ // Optional: if you want DB to be the source of truth for role, uncomment below:
+ // user = { ...user, role: existingUser.role ?? "user" };
}
}
diff --git a/src/pages/user.tsx b/src/pages/user.tsx
index 0fd90651..1d14ccdc 100644
--- a/src/pages/user.tsx
+++ b/src/pages/user.tsx
@@ -21,6 +21,7 @@ export const userService = new Elysia({ name: "user/service" })
name: "jwt",
schema: t.Object({
id: t.String(),
+ role: t.String(), // user role in JWT
}),
secret: process.env.JWT_SECRET ?? randomUUID(),
exp: "7d",
@@ -183,7 +184,10 @@ export const user = new Elysia()
.post(
"/register",
async ({ body: { email, password }, set, redirect, jwt, cookie: { auth } }) => {
- if (!ACCOUNT_REGISTRATION && !FIRST_RUN) {
+ // first user allowed even if ACCOUNT_REGISTRATION=false
+ const isFirstUser = FIRST_RUN;
+
+ if (!ACCOUNT_REGISTRATION && !isFirstUser) {
return redirect(`${WEBROOT}/login`, 302);
}
@@ -200,11 +204,17 @@ export const user = new Elysia()
}
const savedPassword = await Bun.password.hash(password);
- db.query("INSERT INTO users (email, password) VALUES (?, ?)").run(email, savedPassword);
+ const role = isFirstUser ? "admin" : "user";
+
+ db.query("INSERT INTO users (email, password, role) VALUES (?, ?, ?)").run(
+ email,
+ savedPassword,
+ role,
+ );
- const user = db.query("SELECT * FROM users WHERE email = ?").as(User).get(email);
+ const userRow = db.query("SELECT * FROM users WHERE email = ?").as(User).get(email);
- if (!user) {
+ if (!userRow) {
set.status = 500;
return {
message: "Failed to create user.",
@@ -212,7 +222,8 @@ export const user = new Elysia()
}
const accessToken = await jwt.sign({
- id: String(user.id),
+ id: String(userRow.id),
+ role: userRow.role,
});
if (!auth) {
@@ -318,7 +329,9 @@ export const user = new Elysia()
.post(
"/login",
async function handler({ body, set, redirect, jwt, cookie: { auth } }) {
- const existingUser = db.query("SELECT * FROM users WHERE email = ?").as(User).get(body.email);
+ const existingUser = db.query("SELECT * FROM users WHERE email = ?").as(User).get(
+ body.email,
+ );
if (!existingUser) {
set.status = 403;
@@ -338,6 +351,7 @@ export const user = new Elysia()
const accessToken = await jwt.sign({
id: String(existingUser.id),
+ role: existingUser.role ?? "user",
});
if (!auth) {
@@ -387,6 +401,13 @@ export const user = new Elysia()
return redirect(`${WEBROOT}/`, 302);
}
+ let otherUsers: { id: number; email: string; role: string }[] = [];
+ if (userData.role === "admin") {
+ otherUsers = db
+ .query("SELECT id, email, role FROM users WHERE id != ? ORDER BY email ASC")
+ .all(userData.id) as { id: number; email: string; role: string }[];
+ }
+
return (
+ Create additional users for this ConvertX instance. Admins can create other
+ admins or normal users.
+
+ Edit or delete users from this instance. You cannot delete yourself or the
+ last remaining admin.
+
+ Change this user's role or set a new password. Leave password blank to keep
+ it unchanged.
+ Add new user
+ Manage users
+ Edit user
+