Add user change password
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user