Add files via upload
This commit is contained in:
parent
59b2fd19c9
commit
8baa7c2c55
2 changed files with 77 additions and 99 deletions
|
|
@ -76,7 +76,7 @@ export const root = new Elysia().use(userService).get(
|
||||||
// fallback: treat as normal user if role missing
|
// fallback: treat as normal user if role missing
|
||||||
user = { ...verifiedUser, id: verifiedUser.id, role: "user" };
|
user = { ...verifiedUser, id: verifiedUser.id, role: "user" };
|
||||||
} else {
|
} else {
|
||||||
user = verifiedUser as ({ id: string; role: string } & JWTPayloadSpec);
|
user = verifiedUser as { id: string; role: string } & JWTPayloadSpec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -193,88 +193,88 @@ export const user = new Elysia()
|
||||||
</BaseHtml>
|
</BaseHtml>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.post(
|
.post(
|
||||||
"/register",
|
"/register",
|
||||||
async ({ body: { email, password }, set, redirect, jwt, cookie: { auth } }) => {
|
async ({ body: { email, password }, set, redirect, jwt, cookie: { auth } }) => {
|
||||||
// DB-driven "first user" detection (no stale in-memory flag) + race-safe creation.
|
// DB-driven "first user" detection (no stale in-memory flag) + race-safe creation.
|
||||||
// We hash outside the write-lock to keep the lock window short.
|
// We hash outside the write-lock to keep the lock window short.
|
||||||
const savedPassword = await Bun.password.hash(password);
|
const savedPassword = await Bun.password.hash(password);
|
||||||
|
|
||||||
// Acquire a write lock so only one instance can perform "count==0 then insert" at a time.
|
// Acquire a write lock so only one instance can perform "count==0 then insert" at a time.
|
||||||
db.exec("BEGIN IMMEDIATE");
|
db.exec("BEGIN IMMEDIATE");
|
||||||
try {
|
try {
|
||||||
const isFirstUser = computeFirstRun();
|
const isFirstUser = computeFirstRun();
|
||||||
|
|
||||||
// first user allowed even if ACCOUNT_REGISTRATION=false
|
// first user allowed even if ACCOUNT_REGISTRATION=false
|
||||||
if (!ACCOUNT_REGISTRATION && !isFirstUser) {
|
if (!ACCOUNT_REGISTRATION && !isFirstUser) {
|
||||||
db.exec("ROLLBACK");
|
db.exec("ROLLBACK");
|
||||||
return redirect(`${WEBROOT}/login`, 302);
|
return redirect(`${WEBROOT}/login`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingUser = db.query("SELECT 1 FROM users WHERE email = ?").get(email);
|
const existingUser = db.query("SELECT 1 FROM users WHERE email = ?").get(email);
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
db.exec("ROLLBACK");
|
db.exec("ROLLBACK");
|
||||||
set.status = 400;
|
set.status = 400;
|
||||||
return {
|
return {
|
||||||
message: "Email already in use.",
|
message: "Email already in use.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const role = isFirstUser ? "admin" : "user";
|
const role = isFirstUser ? "admin" : "user";
|
||||||
|
|
||||||
db.query("INSERT INTO users (email, password, role) VALUES (?, ?, ?)").run(
|
db.query("INSERT INTO users (email, password, role) VALUES (?, ?, ?)").run(
|
||||||
email,
|
email,
|
||||||
savedPassword,
|
savedPassword,
|
||||||
role,
|
role,
|
||||||
);
|
);
|
||||||
|
|
||||||
const userRow = 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 (!userRow) {
|
if (!userRow) {
|
||||||
db.exec("ROLLBACK");
|
db.exec("ROLLBACK");
|
||||||
set.status = 500;
|
set.status = 500;
|
||||||
return {
|
return {
|
||||||
message: "Failed to create user.",
|
message: "Failed to create user.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
db.exec("COMMIT");
|
db.exec("COMMIT");
|
||||||
FIRST_RUN = false;
|
FIRST_RUN = false;
|
||||||
|
|
||||||
const accessToken = await jwt.sign({
|
const accessToken = await jwt.sign({
|
||||||
id: String(userRow.id),
|
id: String(userRow.id),
|
||||||
role: userRow.role ?? "user",
|
role: userRow.role ?? "user",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
set.status = 500;
|
set.status = 500;
|
||||||
return {
|
return {
|
||||||
message: "No auth cookie, perhaps your browser is blocking cookies.",
|
message: "No auth cookie, perhaps your browser is blocking cookies.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// set cookie
|
// set cookie
|
||||||
auth.set({
|
auth.set({
|
||||||
value: accessToken,
|
value: accessToken,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: !HTTP_ALLOWED,
|
secure: !HTTP_ALLOWED,
|
||||||
maxAge: 60 * 60 * 24 * 7,
|
maxAge: 60 * 60 * 24 * 7,
|
||||||
sameSite: "strict",
|
sameSite: "strict",
|
||||||
});
|
});
|
||||||
|
|
||||||
return redirect(`${WEBROOT}/`, 302);
|
return redirect(`${WEBROOT}/`, 302);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
try {
|
||||||
db.exec("ROLLBACK");
|
db.exec("ROLLBACK");
|
||||||
} catch (rollbackErr) {
|
} catch {
|
||||||
console.warn("[user/register] ROLLBACK failed:", rollbackErr);
|
// ignore rollback errors
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ body: "signIn" },
|
{ body: "signIn" },
|
||||||
)
|
)
|
||||||
.get(
|
.get(
|
||||||
"/login",
|
"/login",
|
||||||
async ({ jwt, redirect, cookie: { auth } }) => {
|
async ({ jwt, redirect, cookie: { auth } }) => {
|
||||||
if (computeFirstRun()) {
|
if (computeFirstRun()) {
|
||||||
|
|
@ -357,9 +357,7 @@ export const user = new Elysia()
|
||||||
.post(
|
.post(
|
||||||
"/login",
|
"/login",
|
||||||
async function handler({ body, set, redirect, jwt, cookie: { auth } }) {
|
async function handler({ body, set, redirect, jwt, cookie: { auth } }) {
|
||||||
const existingUser = db.query("SELECT * FROM users WHERE email = ?").as(User).get(
|
const existingUser = db.query("SELECT * FROM users WHERE email = ?").as(User).get(body.email);
|
||||||
body.email,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!existingUser) {
|
if (!existingUser) {
|
||||||
set.status = 403;
|
set.status = 403;
|
||||||
|
|
@ -535,11 +533,7 @@ export const user = new Elysia()
|
||||||
</label>
|
</label>
|
||||||
<label class="flex flex-col gap-1">
|
<label class="flex flex-col gap-1">
|
||||||
Role
|
Role
|
||||||
<select
|
<select name="newUserRole" class="rounded-sm bg-neutral-800 p-3" required>
|
||||||
name="newUserRole"
|
|
||||||
class="rounded-sm bg-neutral-800 p-3"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value="user">Normal user</option>
|
<option value="user">Normal user</option>
|
||||||
<option value="admin">Admin</option>
|
<option value="admin">Admin</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -583,15 +577,8 @@ export const user = new Elysia()
|
||||||
<td>
|
<td>
|
||||||
<div class="flex items-center gap-6">
|
<div class="flex items-center gap-6">
|
||||||
{/* Edit / details icon */}
|
{/* Edit / details icon */}
|
||||||
<form
|
<form method="get" action={`${WEBROOT}/account/edit-user`}>
|
||||||
method="get"
|
<input type="hidden" name="userId" value={String(u.id)} />
|
||||||
action={`${WEBROOT}/account/edit-user`}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="hidden"
|
|
||||||
name="userId"
|
|
||||||
value={String(u.id)}
|
|
||||||
/>
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class={`
|
class={`
|
||||||
|
|
@ -684,9 +671,7 @@ export const user = new Elysia()
|
||||||
if (!tokenUser) {
|
if (!tokenUser) {
|
||||||
return redirect(`${WEBROOT}/login`, 302);
|
return redirect(`${WEBROOT}/login`, 302);
|
||||||
}
|
}
|
||||||
const existingUser = db.query("SELECT * FROM users WHERE id = ?").as(User).get(
|
const existingUser = db.query("SELECT * FROM users WHERE id = ?").as(User).get(tokenUser.id);
|
||||||
tokenUser.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!existingUser) {
|
if (!existingUser) {
|
||||||
if (auth?.value) {
|
if (auth?.value) {
|
||||||
|
|
@ -875,11 +860,7 @@ export const user = new Elysia()
|
||||||
</label>
|
</label>
|
||||||
<label class="flex flex-col gap-1">
|
<label class="flex flex-col gap-1">
|
||||||
Role
|
Role
|
||||||
<select
|
<select name="role" class="rounded-sm bg-neutral-800 p-3 capitalize" required>
|
||||||
name="role"
|
|
||||||
class="rounded-sm bg-neutral-800 p-3 capitalize"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value="user" selected={targetUser.role === "user"}>
|
<option value="user" selected={targetUser.role === "user"}>
|
||||||
Normal user
|
Normal user
|
||||||
</option>
|
</option>
|
||||||
|
|
@ -900,10 +881,7 @@ export const user = new Elysia()
|
||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="flex flex-row gap-4">
|
<div class="flex flex-row gap-4">
|
||||||
<a
|
<a href={`${WEBROOT}/account`} class="w-full btn-secondary text-center">
|
||||||
href={`${WEBROOT}/account`}
|
|
||||||
class="w-full btn-secondary text-center"
|
|
||||||
>
|
|
||||||
Cancel
|
Cancel
|
||||||
</a>
|
</a>
|
||||||
<button type="submit" class="w-full btn-primary">
|
<button type="submit" class="w-full btn-primary">
|
||||||
|
|
@ -984,9 +962,10 @@ export const user = new Elysia()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fields.length > 0) {
|
if (fields.length > 0) {
|
||||||
db.query(
|
db.query(`UPDATE users SET ${fields.map((f) => `${f}=?`).join(", ")} WHERE id=?`).run(
|
||||||
`UPDATE users SET ${fields.map((f) => `${f}=?`).join(", ")} WHERE id=?`,
|
...values,
|
||||||
).run(...values, targetId);
|
targetId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect(`${WEBROOT}/account`, 302);
|
return redirect(`${WEBROOT}/account`, 302);
|
||||||
|
|
@ -1078,4 +1057,3 @@ export const user = new Elysia()
|
||||||
cookie: "session",
|
cookie: "session",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue