Add files via upload
This commit is contained in:
parent
48fefdd5be
commit
03e964bad7
1 changed files with 53 additions and 69 deletions
|
|
@ -13,9 +13,19 @@ import {
|
||||||
WEBROOT,
|
WEBROOT,
|
||||||
} from "../helpers/env";
|
} from "../helpers/env";
|
||||||
|
|
||||||
export let FIRST_RUN = db.query("SELECT * FROM users").get() === null || false;
|
function computeFirstRun(): boolean {
|
||||||
|
// DB-driven, so it stays correct across reloads and multi-process setups.
|
||||||
|
return db.query("SELECT 1 FROM users LIMIT 1").get() === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exported for backwards compatibility; refreshed per-request via userService.derive().
|
||||||
|
export let FIRST_RUN = computeFirstRun();
|
||||||
|
|
||||||
export const userService = new Elysia({ name: "user/service" })
|
export const userService = new Elysia({ name: "user/service" })
|
||||||
|
.derive(() => {
|
||||||
|
FIRST_RUN = computeFirstRun();
|
||||||
|
return {};
|
||||||
|
})
|
||||||
.use(
|
.use(
|
||||||
jwt({
|
jwt({
|
||||||
name: "jwt",
|
name: "jwt",
|
||||||
|
|
@ -67,7 +77,7 @@ export const userService = new Elysia({ name: "user/service" })
|
||||||
export const user = new Elysia()
|
export const user = new Elysia()
|
||||||
.use(userService)
|
.use(userService)
|
||||||
.get("/setup", ({ redirect }) => {
|
.get("/setup", ({ redirect }) => {
|
||||||
if (!FIRST_RUN) {
|
if (!computeFirstRun()) {
|
||||||
return redirect(`${WEBROOT}/login`, 302);
|
return redirect(`${WEBROOT}/login`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,25 +194,26 @@ export const user = new Elysia()
|
||||||
.post(
|
.post(
|
||||||
"/register",
|
"/register",
|
||||||
async ({ body: { email, password }, set, redirect, jwt, cookie: { auth } }) => {
|
async ({ body: { email, password }, set, redirect, jwt, cookie: { auth } }) => {
|
||||||
// first user allowed even if ACCOUNT_REGISTRATION=false
|
// DB-driven "first user" detection + race-safe creation.
|
||||||
const isFirstUser = FIRST_RUN;
|
// Hash outside the write-lock to keep the lock window short.
|
||||||
|
const savedPassword = await Bun.password.hash(password);
|
||||||
|
|
||||||
|
db.exec("BEGIN IMMEDIATE");
|
||||||
|
try {
|
||||||
|
const isFirstUser = computeFirstRun();
|
||||||
|
|
||||||
|
// first user allowed even if ACCOUNT_REGISTRATION=false
|
||||||
if (!ACCOUNT_REGISTRATION && !isFirstUser) {
|
if (!ACCOUNT_REGISTRATION && !isFirstUser) {
|
||||||
|
db.exec("ROLLBACK");
|
||||||
return redirect(`${WEBROOT}/login`, 302);
|
return redirect(`${WEBROOT}/login`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FIRST_RUN) {
|
const existingUser = db.query("SELECT 1 FROM users WHERE email = ?").get(email);
|
||||||
FIRST_RUN = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingUser = await db.query("SELECT * FROM users WHERE email = ?").get(email);
|
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
|
db.exec("ROLLBACK");
|
||||||
set.status = 400;
|
set.status = 400;
|
||||||
return {
|
return { message: "Email already in use." };
|
||||||
message: "Email already in use.",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
const savedPassword = await Bun.password.hash(password);
|
|
||||||
|
|
||||||
const role = isFirstUser ? "admin" : "user";
|
const role = isFirstUser ? "admin" : "user";
|
||||||
|
|
||||||
|
|
@ -213,24 +224,25 @@ export const user = new Elysia()
|
||||||
);
|
);
|
||||||
|
|
||||||
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");
|
||||||
set.status = 500;
|
set.status = 500;
|
||||||
return {
|
return { message: "Failed to create user." };
|
||||||
message: "Failed to create user.",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db.exec("COMMIT");
|
||||||
|
|
||||||
|
// Refresh after successful creation
|
||||||
|
FIRST_RUN = computeFirstRun();
|
||||||
|
|
||||||
const accessToken = await jwt.sign({
|
const accessToken = await jwt.sign({
|
||||||
id: String(userRow.id),
|
id: String(userRow.id),
|
||||||
role: userRow.role,
|
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
|
||||||
|
|
@ -243,9 +255,18 @@ export const user = new Elysia()
|
||||||
});
|
});
|
||||||
|
|
||||||
return redirect(`${WEBROOT}/`, 302);
|
return redirect(`${WEBROOT}/`, 302);
|
||||||
|
} catch (e) {
|
||||||
|
try {
|
||||||
|
db.exec("ROLLBACK");
|
||||||
|
} catch (rollbackErr) {
|
||||||
|
console.warn("[user/register] ROLLBACK failed:", rollbackErr);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ body: "signIn" },
|
{ body: "signIn" },
|
||||||
)
|
)
|
||||||
|
|
||||||
.get(
|
.get(
|
||||||
"/login",
|
"/login",
|
||||||
async ({ jwt, redirect, cookie: { auth } }) => {
|
async ({ jwt, redirect, cookie: { auth } }) => {
|
||||||
|
|
@ -547,35 +568,18 @@ export const user = new Elysia()
|
||||||
<td>{u.email}</td>
|
<td>{u.email}</td>
|
||||||
<td class="capitalize">{u.role}</td>
|
<td class="capitalize">{u.role}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="flex items-center gap-6">
|
<div class="flex flex-wrap items-center gap-3">
|
||||||
{/* Edit / details icon */}
|
|
||||||
<form method="get" action={`${WEBROOT}/account/edit-user`}>
|
<form method="get" action={`${WEBROOT}/account/edit-user`}>
|
||||||
<input type="hidden" name="userId" value={String(u.id)} />
|
<input type="hidden" name="userId" value={String(u.id)} />
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class={`
|
class="btn-secondary px-3 py-2"
|
||||||
inline-flex items-center justify-center text-accent-400
|
|
||||||
hover:text-accent-500
|
|
||||||
`}
|
|
||||||
title="Edit user"
|
title="Edit user"
|
||||||
>
|
>
|
||||||
<svg
|
Edit
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
class="h-6 w-6"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="1.8"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path d="M2.458 12C3.732 7.943 7.523 5 12 5s8.268 2.943 9.542 7c-1.274 4.057-5.065 7-9.542 7s-8.268-2.943-9.542-7z" />
|
|
||||||
<circle cx="12" cy="12" r="3" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{/* Delete icon */}
|
|
||||||
<form
|
<form
|
||||||
method="post"
|
method="post"
|
||||||
action={`${WEBROOT}/account/delete-user`}
|
action={`${WEBROOT}/account/delete-user`}
|
||||||
|
|
@ -588,28 +592,10 @@ export const user = new Elysia()
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class={`
|
class="btn-secondary px-3 py-2"
|
||||||
inline-flex items-center justify-center text-accent-400
|
|
||||||
hover:text-accent-500
|
|
||||||
`}
|
|
||||||
title="Delete user"
|
title="Delete user"
|
||||||
>
|
>
|
||||||
<svg
|
Delete
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
class="h-6 w-6"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="1.8"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path d="M4 7h16" />
|
|
||||||
<path d="M10 11v6" />
|
|
||||||
<path d="M14 11v6" />
|
|
||||||
<path d="M6 7l1 12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-12" />
|
|
||||||
<path d="M9 4h6a1 1 0 0 1 1 1v2H8V5a1 1 0 0 1 1-1z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -817,9 +803,7 @@ export const user = new Elysia()
|
||||||
<form
|
<form
|
||||||
method="post"
|
method="post"
|
||||||
action={`${WEBROOT}/account/edit-user`}
|
action={`${WEBROOT}/account/edit-user`}
|
||||||
class={`
|
class="flex flex-col gap-4"
|
||||||
flex flex-col gap-4
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
<input type="hidden" name="userId" value={String(targetUser.id)} />
|
<input type="hidden" name="userId" value={String(targetUser.id)} />
|
||||||
<fieldset class="mb-4 flex flex-col gap-4">
|
<fieldset class="mb-4 flex flex-col gap-4">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue