implement password change
This commit is contained in:
parent
9ffbdae1b6
commit
55d9e84431
|
@ -4,7 +4,7 @@ import './globals.css';
|
|||
import AuthWrapper from '@/lib/components/AuthWrapper';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import NavigationPanel from '@/lib/components/ui/NavigationPanel';
|
||||
import { Flex } from '@radix-ui/themes';
|
||||
import { Flex, ThemePanel } from '@radix-ui/themes';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Create Next App',
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import { Card, Text, Heading, Flex, TextField, Button, IconButton, Popover, Link, Tabs, Box, Grid } from "@radix-ui/themes";
|
||||
import { CheckIcon, CopyIcon, InfoIcon, LockIcon, UserIcon } from "lucide-react";
|
||||
import { changeOwnPassword } from "@/lib/actions";
|
||||
import { Card, Text, Heading, Flex, TextField, Button, IconButton, Popover, Link, Tabs, Box, Grid, Dialog, Callout, Tooltip } from "@radix-ui/themes";
|
||||
import { AlertCircleIcon, CheckIcon, CopyIcon, ExternalLinkIcon, InfoIcon, LockIcon, UserIcon } from "lucide-react";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
// TODO read these from environment
|
||||
const EMAIL_HOST = "mx1.amogus.cloud";
|
||||
|
@ -11,10 +12,15 @@ const SMTP_PORT = "465";
|
|||
const IMAP_PORT = "993";
|
||||
const SMTP_SECURITY = "SSL/TLS";
|
||||
const IMAP_SECURITY = "SSL/TLS";
|
||||
const WEBMAIL_URL = "https://mail.amogus.cloud";
|
||||
|
||||
export default function SelfService() {
|
||||
const session = useSession().data;
|
||||
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const validPassword = newPassword.length >= 8;
|
||||
const [passwordChanged, setPasswordChanged] = useState(false);
|
||||
|
||||
function StaticValueField({ label, value }: { label: string, value: string }) {
|
||||
const [checked, setChecked] = useState(false);
|
||||
|
||||
|
@ -44,8 +50,31 @@ export default function SelfService() {
|
|||
);
|
||||
}
|
||||
|
||||
function PasswordChangedAlert() {
|
||||
const [hide, setHide] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let timeout = setTimeout(() => {
|
||||
setHide(true);
|
||||
setTimeout(() => setPasswordChanged(false), 500);
|
||||
}, 5000);
|
||||
return () => clearTimeout(timeout);
|
||||
});
|
||||
|
||||
return (
|
||||
<Callout.Root className={`transition-all ${hide ? "opacity-0 h-0 m-0 !p-0" : " mb-4"}`}>
|
||||
<Callout.Icon><CheckIcon /></Callout.Icon>
|
||||
<Callout.Text>Password updated!</Callout.Text>
|
||||
</Callout.Root>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{passwordChanged && (
|
||||
<PasswordChangedAlert />
|
||||
)}
|
||||
|
||||
<Heading className="pb-4">Account settings</Heading>
|
||||
|
||||
<Grid display="inline-grid" rows="1" columns="2" gap="4">
|
||||
|
@ -83,9 +112,69 @@ export default function SelfService() {
|
|||
<TextField.Input disabled value={"\u25CF".repeat(10)} />
|
||||
</TextField.Root>
|
||||
|
||||
<Button variant="outline">
|
||||
Change
|
||||
</Button>
|
||||
<Dialog.Root>
|
||||
<Dialog.Trigger>
|
||||
<Button variant="outline">
|
||||
Change
|
||||
</Button>
|
||||
</Dialog.Trigger>
|
||||
|
||||
<Dialog.Content>
|
||||
<Dialog.Title>Update password</Dialog.Title>
|
||||
|
||||
<Box pb="4">
|
||||
<Callout.Root>
|
||||
<Callout.Icon>
|
||||
<AlertCircleIcon />
|
||||
</Callout.Icon>
|
||||
<Callout.Text>You will be logged out of every configured mail client!</Callout.Text>
|
||||
</Callout.Root>
|
||||
</Box>
|
||||
|
||||
<TextField.Root>
|
||||
<TextField.Slot>
|
||||
<LockIcon height="16" width="16" />
|
||||
</TextField.Slot>
|
||||
<TextField.Input
|
||||
type="password"
|
||||
placeholder="New password"
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.currentTarget.value)}
|
||||
/>
|
||||
</TextField.Root>
|
||||
|
||||
<Flex gap="3" mt="4" justify="end">
|
||||
<Dialog.Close>
|
||||
<Button variant="outline" onClick={() => setNewPassword("")}>Cancel</Button>
|
||||
</Dialog.Close>
|
||||
<Dialog.Close>
|
||||
{validPassword
|
||||
? (
|
||||
<Button
|
||||
variant="solid"
|
||||
onClick={async () => {
|
||||
setNewPassword("");
|
||||
|
||||
try {
|
||||
setPasswordChanged(false);
|
||||
await changeOwnPassword(newPassword);
|
||||
setPasswordChanged(true);
|
||||
} catch(e) {
|
||||
alert(e);
|
||||
}
|
||||
}}
|
||||
>Update</Button>
|
||||
)
|
||||
: (
|
||||
<Tooltip content="The password must have a minimum length of 8 characters">
|
||||
<Button variant="solid" disabled>Update</Button>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
</Dialog.Close>
|
||||
</Flex>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
|
@ -99,6 +188,11 @@ export default function SelfService() {
|
|||
<Tabs.List size="2">
|
||||
<Tabs.Trigger value="smtp">Outgoing</Tabs.Trigger>
|
||||
<Tabs.Trigger value="imap">Incoming</Tabs.Trigger>
|
||||
<Flex align="center" justify="end" grow="1" pr="3">
|
||||
<Link href={WEBMAIL_URL} target="_blank">
|
||||
<Button variant="ghost">Webmail <ExternalLinkIcon size="14" /></Button>
|
||||
</Link>
|
||||
</Flex>
|
||||
</Tabs.List>
|
||||
|
||||
<Box pt="3" pb="2">
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"use server";
|
||||
|
||||
import { database } from "./db";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { database, setUserPassword } from "./db";
|
||||
import { isAdmin } from "./util";
|
||||
|
||||
export async function fetchAllUsers(): Promise<string[]> {
|
||||
|
@ -15,3 +16,10 @@ export async function fetchAllUsers(): Promise<string[]> {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function changeOwnPassword(newPass: string) {
|
||||
if (newPass.length < 8) throw new Error("Invalid password");
|
||||
const session = await getServerSession();
|
||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||
await setUserPassword(session.user.email, newPass);
|
||||
}
|
||||
|
|
|
@ -32,3 +32,19 @@ export function validateCredentials(email: string, password: string) {
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function setUserPassword(email: string, newPass: string) {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
const db = database();
|
||||
const hash = 'bcrypt:' + await bcrypt.hash(newPass, 10);
|
||||
|
||||
db.run("UPDATE passwords SET value = $1 WHERE key = $2",
|
||||
{ 1: hash, 2: email },
|
||||
async (err: any, res: any) => {
|
||||
db.close();
|
||||
if (err) return reject(err);
|
||||
console.log(res);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue