Add user change password
This commit is contained in:
@@ -83,7 +83,7 @@ export default function HostWgIpField({
|
|||||||
return (
|
return (
|
||||||
<Stack className="w-full gap-2">
|
<Stack className="w-full gap-2">
|
||||||
<Input
|
<Input
|
||||||
defaultValue={value}
|
value={value}
|
||||||
changeHandler={(v) => {
|
changeHandler={(v) => {
|
||||||
onChange?.(v);
|
onChange?.(v);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -16,14 +16,20 @@ export default function UserFormUserTypes({
|
|||||||
).map((ut) => ({
|
).map((ut) => ({
|
||||||
value: ut.value,
|
value: ut.value,
|
||||||
}))}
|
}))}
|
||||||
defaultValues={form.user_types || []}
|
defaultValues={
|
||||||
|
(form.user_types as unknown as UserType[] | undefined) ||
|
||||||
|
[]
|
||||||
|
}
|
||||||
changeHandler={(value) => {
|
changeHandler={(value) => {
|
||||||
const user_types = Array.isArray(value)
|
const user_types = Array.isArray(value)
|
||||||
? (value as UserType[])
|
? (value as UserType[])
|
||||||
: value
|
: value
|
||||||
? ([value] as UserType[])
|
? ([value] as UserType[])
|
||||||
: [];
|
: [];
|
||||||
setForm({ ...form, user_types });
|
setForm({
|
||||||
|
...form,
|
||||||
|
user_types: user_types.join(","),
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
title="User Types"
|
title="User Types"
|
||||||
showLabel
|
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 Divider from "@/src/components/twui/layout/Divider";
|
||||||
import Stack from "@/src/components/twui/layout/Stack";
|
import Stack from "@/src/components/twui/layout/Stack";
|
||||||
import SettingsFormSection from "./(sections)/settings-form-section";
|
import SettingsFormSection from "./(sections)/settings-form-section";
|
||||||
|
import ChangePasswordSection from "./(sections)/change-password-section";
|
||||||
|
|
||||||
export default function AdminSettingsPage() {
|
export default function AdminSettingsPage() {
|
||||||
return (
|
return (
|
||||||
@@ -15,6 +16,7 @@ export default function AdminSettingsPage() {
|
|||||||
<Divider className="mb-6" />
|
<Divider className="mb-6" />
|
||||||
<Stack className="w-full px-6 pb-8 gap-5 items-stretch">
|
<Stack className="w-full px-6 pb-8 gap-5 items-stretch">
|
||||||
<SettingsFormSection />
|
<SettingsFormSection />
|
||||||
|
<ChangePasswordSection />
|
||||||
</Stack>
|
</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;
|
email_or_username?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
};
|
};
|
||||||
|
current_password?: string;
|
||||||
|
new_password?: string;
|
||||||
|
confirm_password?: string;
|
||||||
contact?: {
|
contact?: {
|
||||||
name?: string;
|
name?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user