Add user change password

This commit is contained in:
2026-09-20 11:48:02 +01:00
parent 7910a578e6
commit 25b64281f8
6 changed files with 228 additions and 3 deletions
+1 -1
View File
@@ -83,7 +83,7 @@ export default function HostWgIpField({
return (
<Stack className="w-full gap-2">
<Input
defaultValue={value}
value={value}
changeHandler={(v) => {
onChange?.(v);
}}
@@ -16,14 +16,20 @@ export default function UserFormUserTypes({
).map((ut) => ({
value: ut.value,
}))}
defaultValues={form.user_types || []}
defaultValues={
(form.user_types as unknown as UserType[] | undefined) ||
[]
}
changeHandler={(value) => {
const user_types = Array.isArray(value)
? (value as UserType[])
: value
? ([value] as UserType[])
: [];
setForm({ ...form, user_types });
setForm({
...form,
user_types: user_types.join(","),
});
}}
title="User Types"
showLabel
@@ -0,0 +1,140 @@
import { useState } from "react";
import { CircleCheck, TriangleAlert } from "lucide-react";
import AdminCard from "@/src/components/general/admin-card";
import useStatus from "@/src/components/twui/hooks/useStatus";
import fetchApi from "@/src/components/twui/utils/fetch/fetchApi";
import Input from "@/src/components/twui/form/Input";
import H3 from "@/src/components/twui/layout/H3";
import P from "@/src/components/twui/layout/P";
import Row from "@/src/components/twui/layout/Row";
import Span from "@/src/components/twui/layout/Span";
import Stack from "@/src/components/twui/layout/Stack";
import Tag from "@/src/components/twui/elements/Tag";
import Button from "@/src/components/twui/layout/Button";
import type { ApiReqParams } from "@/src/types";
import type { APIResponseObject } from "@moduletrace/bunext/types";
export default function ChangePasswordSection() {
const { loading, setLoading, status, setStatus } = useStatus();
const [new_password, setNewPassword] = useState("");
const [confirm_password, setConfirmPassword] = useState("");
function handleChangePassword() {
if (!window.confirm("Change your password?")) return;
setStatus(undefined);
setLoading(true);
fetchApi<ApiReqParams, APIResponseObject>(
`/api/admin/change-password`,
{
method: "POST",
body: {
new_password,
confirm_password,
},
},
)
.then((res) => {
if (res.success) {
setNewPassword("");
setConfirmPassword("");
setStatus({
success: true,
msg: res.msg || "Password updated",
});
return;
}
setStatus({
error: true,
success: false,
msg: res.msg || "Could not update the password",
});
})
.catch((error: any) => {
setStatus({
error: true,
success: false,
msg: error.message || "Could not update the password",
});
})
.finally(() => {
setLoading(false);
});
}
return (
<AdminCard className="w-full p-5 flex flex-col gap-4">
<Stack className="gap-1">
<H3 className="text-[14px] font-semibold mb-0!">
Change password
</H3>
<P
noMargin
className="text-[12.5px] text-foreground-light/45 dark:text-foreground-dark/45"
>
Choose a strong password you don't use anywhere else.
</P>
</Stack>
<Input
type="password"
placeholder="New password"
autoComplete="new-password"
showLabel
onChange={(e) => {
setNewPassword(e.target.value);
}}
value={new_password}
/>
<Input
type="password"
placeholder="Confirm new password"
autoComplete="new-password"
showLabel
onChange={(e) => {
setConfirmPassword(e.target.value);
}}
value={confirm_password}
/>
{status?.error && status.msg ? (
<Tag
variant="outlined"
color="error"
className="w-full py-1.5 outline-error/50 bg-error/5!"
>
<Row>
<TriangleAlert size={15} />
<Span>{status.msg}</Span>
</Row>
</Tag>
) : null}
{status?.success && status.msg ? (
<Tag
variant="outlined"
color="success"
className="w-full py-1.5 outline-success/50 bg-success/5!"
>
<Row>
<CircleCheck size={15} />
<Span>{status.msg}</Span>
</Row>
</Tag>
) : null}
<Row className="w-full items-center gap-2">
<Button
title="Update Password"
disabled={loading || !new_password}
loading={loading}
onClick={handleChangePassword}
>
Update Password
</Button>
</Row>
</AdminCard>
);
}
+2
View File
@@ -4,6 +4,7 @@ import { SiteData } from "@/src/data/site-data";
import Divider from "@/src/components/twui/layout/Divider";
import Stack from "@/src/components/twui/layout/Stack";
import SettingsFormSection from "./(sections)/settings-form-section";
import ChangePasswordSection from "./(sections)/change-password-section";
export default function AdminSettingsPage() {
return (
@@ -15,6 +16,7 @@ export default function AdminSettingsPage() {
<Divider className="mb-6" />
<Stack className="w-full px-6 pb-8 gap-5 items-stretch">
<SettingsFormSection />
<ChangePasswordSection />
</Stack>
</>
);
+74
View File
@@ -0,0 +1,74 @@
import type { BUN_SQLITE_WGUI_USERS } from "@/db/types/db";
import { AppData } from "@/src/data/app-data";
import userAuth from "@/src/functions/backend/auth/user-auth";
import type { ApiReqParams, TableType } from "@/src/types";
import BunSQLite from "@moduletrace/bun-sqlite";
import type {
APIResponseObject,
BunextAPIRouteHandler,
} from "@moduletrace/bunext/types";
export const handler: BunextAPIRouteHandler<APIResponseObject> = async (
params,
) => {
const req = params.req;
const body = params.body as ApiReqParams;
if (req.method !== "POST") {
return {
success: false,
};
}
const { user } = await userAuth({ req });
if (!user?.logged_in_status || !user.id) {
return {
success: false,
msg: `Unauthorized`,
logoutUser: true,
};
}
try {
const { new_password, confirm_password } = body;
if (!new_password) {
throw new Error(`Please enter a new password`);
}
if (new_password !== confirm_password) {
throw new Error(`New passwords don't match`);
}
const new_password_hash = await Bun.password.hash(new_password, {
algorithm: "bcrypt",
cost: AppData["HashCost"],
});
const update_res = await BunSQLite.update<
BUN_SQLITE_WGUI_USERS,
TableType
>({
table: "users",
data: {
password: new_password_hash,
},
targetId: user.id,
});
if (!update_res.success) {
throw new Error(`Couldn't update password`);
}
return {
success: true,
msg: `Password updated successfully`,
};
} catch (error: any) {
return {
success: false,
msg: error.message,
};
}
};
+3
View File
@@ -200,6 +200,9 @@ export type ApiReqParams<
email_or_username?: string;
password?: string;
};
current_password?: string;
new_password?: string;
confirm_password?: string;
contact?: {
name?: string;
email?: string;