mirror of
https://github.com/fosrl/pangolin.git
synced 2025-07-28 14:44:55 +02:00
send confirm password reset email
This commit is contained in:
parent
4b34353354
commit
af2d78cbfb
7 changed files with 114 additions and 13 deletions
70
server/emails/templates/NotifyResetPassword.tsx
Normal file
70
server/emails/templates/NotifyResetPassword.tsx
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Container,
|
||||||
|
Head,
|
||||||
|
Heading,
|
||||||
|
Html,
|
||||||
|
Preview,
|
||||||
|
Section,
|
||||||
|
Text,
|
||||||
|
Tailwind
|
||||||
|
} from "@react-email/components";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConfirmPasswordReset = ({ email }: Props) => {
|
||||||
|
const previewText = `Your password has been reset`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Html>
|
||||||
|
<Head />
|
||||||
|
<Preview>{previewText}</Preview>
|
||||||
|
<Tailwind
|
||||||
|
config={{
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
primary: "#16A34A"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Body className="font-sans">
|
||||||
|
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8 rounded-lg">
|
||||||
|
<Heading className="text-2xl font-semibold text-gray-800 text-center">
|
||||||
|
Your password has been successfully reset
|
||||||
|
</Heading>
|
||||||
|
<Text className="text-base text-gray-700 mt-4">
|
||||||
|
Hi {email || "there"},
|
||||||
|
</Text>
|
||||||
|
<Text className="text-base text-gray-700 mt-2">
|
||||||
|
This email confirms that your password has just been
|
||||||
|
reset. If you made this change, no further action is
|
||||||
|
required.
|
||||||
|
</Text>
|
||||||
|
<Section className="text-center my-6">
|
||||||
|
<Text className="text-base text-gray-700">
|
||||||
|
If you did not request this change, please
|
||||||
|
contact our support team immediately.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
<Text className="text-base text-gray-700 mt-2">
|
||||||
|
Thank you for keeping your account secure.
|
||||||
|
</Text>
|
||||||
|
<Text className="text-sm text-gray-500 mt-6">
|
||||||
|
Best regards,
|
||||||
|
<br />
|
||||||
|
Fossorial
|
||||||
|
</Text>
|
||||||
|
</Container>
|
||||||
|
</Body>
|
||||||
|
</Tailwind>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmPasswordReset;
|
|
@ -48,8 +48,8 @@ export const ResetPasswordCode = ({ email, code, link }: Props) => {
|
||||||
<a href={link} className="text-primary">
|
<a href={link} className="text-primary">
|
||||||
click here
|
click here
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
and follow the instructions to reset your
|
and follow the instructions to reset your password,
|
||||||
password, or manually enter the following code:
|
or manually enter the following code:
|
||||||
</Text>
|
</Text>
|
||||||
<Section className="text-center my-6">
|
<Section className="text-center my-6">
|
||||||
<Text className="inline-block bg-primary text-xl font-bold text-white py-2 px-4 border border-gray-300 rounded-xl">
|
<Text className="inline-block bg-primary text-xl font-bold text-white py-2 px-4 border border-gray-300 rounded-xl">
|
||||||
|
@ -60,6 +60,11 @@ export const ResetPasswordCode = ({ email, code, link }: Props) => {
|
||||||
If you didn’t request this, you can safely ignore
|
If you didn’t request this, you can safely ignore
|
||||||
this email.
|
this email.
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text className="text-sm text-gray-500 mt-6">
|
||||||
|
Best regards,
|
||||||
|
<br />
|
||||||
|
Fossorial
|
||||||
|
</Text>
|
||||||
</Container>
|
</Container>
|
||||||
</Body>
|
</Body>
|
||||||
</Tailwind>
|
</Tailwind>
|
||||||
|
|
|
@ -61,6 +61,12 @@ export const ResourceOTPCode = ({
|
||||||
{otp}
|
{otp}
|
||||||
</Text>
|
</Text>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
|
<Text className="text-sm text-gray-500 mt-6">
|
||||||
|
Best regards,
|
||||||
|
<br />
|
||||||
|
Fossorial
|
||||||
|
</Text>
|
||||||
</Container>
|
</Container>
|
||||||
</Body>
|
</Body>
|
||||||
</Tailwind>
|
</Tailwind>
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
Section,
|
Section,
|
||||||
Text,
|
Text,
|
||||||
Tailwind,
|
Tailwind,
|
||||||
Button,
|
Button
|
||||||
} from "@react-email/components";
|
} from "@react-email/components";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ export const SendInviteLink = ({
|
||||||
inviteLink,
|
inviteLink,
|
||||||
orgName,
|
orgName,
|
||||||
inviterName,
|
inviterName,
|
||||||
expiresInDays,
|
expiresInDays
|
||||||
}: SendInviteLinkProps) => {
|
}: SendInviteLinkProps) => {
|
||||||
const previewText = `${inviterName} invited to join ${orgName}`;
|
const previewText = `${inviterName} invited to join ${orgName}`;
|
||||||
|
|
||||||
|
@ -33,15 +33,17 @@ export const SendInviteLink = ({
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
<Preview>{previewText}</Preview>
|
<Preview>{previewText}</Preview>
|
||||||
<Tailwind config={{
|
<Tailwind
|
||||||
theme: {
|
config={{
|
||||||
extend: {
|
theme: {
|
||||||
colors: {
|
extend: {
|
||||||
primary: "#F97317"
|
colors: {
|
||||||
|
primary: "#F97317"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}}
|
||||||
}}>
|
>
|
||||||
<Body className="font-sans">
|
<Body className="font-sans">
|
||||||
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8 rounded-lg">
|
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8 rounded-lg">
|
||||||
<Heading className="text-2xl font-semibold text-gray-800 text-center">
|
<Heading className="text-2xl font-semibold text-gray-800 text-center">
|
||||||
|
@ -71,6 +73,12 @@ export const SendInviteLink = ({
|
||||||
Accept invitation to {orgName}
|
Accept invitation to {orgName}
|
||||||
</Button>
|
</Button>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
|
<Text className="text-sm text-gray-500 mt-6">
|
||||||
|
Best regards,
|
||||||
|
<br />
|
||||||
|
Fossorial
|
||||||
|
</Text>
|
||||||
</Container>
|
</Container>
|
||||||
</Body>
|
</Body>
|
||||||
</Tailwind>
|
</Tailwind>
|
||||||
|
|
|
@ -63,6 +63,11 @@ export const VerifyEmail = ({
|
||||||
If you didn’t request this, you can safely ignore
|
If you didn’t request this, you can safely ignore
|
||||||
this email.
|
this email.
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text className="text-sm text-gray-500 mt-6">
|
||||||
|
Best regards,
|
||||||
|
<br />
|
||||||
|
Fossorial
|
||||||
|
</Text>
|
||||||
</Container>
|
</Container>
|
||||||
</Body>
|
</Body>
|
||||||
</Tailwind>
|
</Tailwind>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import config from "@server/config";
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
@ -15,6 +16,8 @@ import { encodeHex } from "oslo/encoding";
|
||||||
import { isWithinExpirationDate } from "oslo";
|
import { isWithinExpirationDate } from "oslo";
|
||||||
import { invalidateAllSessions } from "@server/auth";
|
import { invalidateAllSessions } from "@server/auth";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
import ConfirmPasswordReset from "@server/emails/templates/NotifyResetPassword";
|
||||||
|
import { sendEmail } from "@server/emails";
|
||||||
|
|
||||||
export const resetPasswordBody = z
|
export const resetPasswordBody = z
|
||||||
.object({
|
.object({
|
||||||
|
@ -141,7 +144,11 @@ export async function resetPassword(
|
||||||
.delete(passwordResetTokens)
|
.delete(passwordResetTokens)
|
||||||
.where(eq(passwordResetTokens.email, email));
|
.where(eq(passwordResetTokens.email, email));
|
||||||
|
|
||||||
// TODO: send email to user confirming password reset
|
await sendEmail(ConfirmPasswordReset({ email }), {
|
||||||
|
from: config.email?.no_reply,
|
||||||
|
to: email,
|
||||||
|
subject: "Password Reset Confirmation"
|
||||||
|
})
|
||||||
|
|
||||||
return response<ResetPasswordResponse>(res, {
|
return response<ResetPasswordResponse>(res, {
|
||||||
data: null,
|
data: null,
|
||||||
|
|
|
@ -62,7 +62,7 @@ export default function LoginForm({ redirect, onLogin }: LoginFormProps) {
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const [mfaRequested, setMfaRequested] = useState(true);
|
const [mfaRequested, setMfaRequested] = useState(false);
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue