diff --git a/src/crud/email.ts b/src/crud/email.ts index 7ebb7e526..704a1f77c 100644 --- a/src/crud/email.ts +++ b/src/crud/email.ts @@ -1,4 +1,9 @@ -import { query, tableValues, setValues, removeReadOnlyValues } from "../helpers/mysql"; +import { + query, + tableValues, + setValues, + removeReadOnlyValues +} from "../helpers/mysql"; import { Email } from "../interfaces/tables/emails"; import { dateToDateTime } from "../helpers/utils"; import { KeyValue } from "../interfaces/general"; @@ -41,6 +46,15 @@ export const sendEmailVerification = async ( return; }; +export const resendEmailVerification = async (id: number) => { + const token = await emailVerificationToken(id); + const emailObject = await getEmail(id); + const email = emailObject.email; + const user = await getUser(emailObject.userId); + await mail(email, Templates.EMAIL_VERIFY, { name: user.name, email, token }); + return; +}; + export const updateEmail = async (id: number, email: KeyValue) => { email.updatedAt = dateToDateTime(new Date()); email = removeReadOnlyValues(email); @@ -87,7 +101,7 @@ export const getEmailObject = async (email: string) => { }; export const getUserVerifiedEmails = async (userId: number) => { - return ( + return ( await query("SELECT * FROM emails WHERE userId = ? AND isVerified = 1", [ userId ]) diff --git a/src/crud/membership.ts b/src/crud/membership.ts index f44102822..316046da8 100644 --- a/src/crud/membership.ts +++ b/src/crud/membership.ts @@ -1,4 +1,9 @@ -import { query, tableValues, setValues, removeReadOnlyValues } from "../helpers/mysql"; +import { + query, + tableValues, + setValues, + removeReadOnlyValues +} from "../helpers/mysql"; import { Membership } from "../interfaces/tables/memberships"; import { dateToDateTime } from "../helpers/utils"; import { KeyValue } from "../interfaces/general"; diff --git a/src/crud/organization.ts b/src/crud/organization.ts index ba0e56479..33a4bf463 100644 --- a/src/crud/organization.ts +++ b/src/crud/organization.ts @@ -1,4 +1,9 @@ -import { query, tableValues, setValues, removeReadOnlyValues } from "../helpers/mysql"; +import { + query, + tableValues, + setValues, + removeReadOnlyValues +} from "../helpers/mysql"; import { Organization } from "../interfaces/tables/organization"; import { capitalizeFirstAndLastLetter, dateToDateTime } from "../helpers/utils"; import { KeyValue } from "../interfaces/general"; diff --git a/src/crud/user.ts b/src/crud/user.ts index 53e059e54..ddbc35c8c 100644 --- a/src/crud/user.ts +++ b/src/crud/user.ts @@ -1,4 +1,9 @@ -import { query, tableValues, setValues, removeReadOnlyValues } from "../helpers/mysql"; +import { + query, + tableValues, + setValues, + removeReadOnlyValues +} from "../helpers/mysql"; import { User } from "../interfaces/tables/user"; import { capitalizeFirstAndLastLetter, diff --git a/src/helpers/mysql.ts b/src/helpers/mysql.ts index aac0b1cda..33b8418bd 100644 --- a/src/helpers/mysql.ts +++ b/src/helpers/mysql.ts @@ -97,4 +97,4 @@ export const removeReadOnlyValues = (object: KeyValue) => { if (object[value]) delete object[value]; }); return object; -} +}; diff --git a/src/interfaces/enum.ts b/src/interfaces/enum.ts index c16666120..7c07b71dd 100644 --- a/src/interfaces/enum.ts +++ b/src/interfaces/enum.ts @@ -34,7 +34,8 @@ export enum EventType { EMAIL_CREATED = "email.created", EMAIL_UPDATED = "email.updated", EMAIL_DELETED = "email.deleted", - EMAIL_VERIFIED = "email.verified" + EMAIL_VERIFIED = "email.verified", + EMAIL_CANNOT_DELETE = "email.cannotDelete" } export enum ErrorCode { diff --git a/src/rest/user.ts b/src/rest/user.ts index a2dfba4ab..4ec7fb90a 100644 --- a/src/rest/user.ts +++ b/src/rest/user.ts @@ -4,12 +4,19 @@ import { UserRole, EventType } from "../interfaces/enum"; -import { getUser } from "../crud/user"; +import { getUser, updateUser } from "../crud/user"; import { getUserOrganizationId, getUserMembershipObject } from "../crud/membership"; -import { createEmail, deleteEmail, getEmail } from "../crud/email"; +import { + createEmail, + deleteEmail, + getEmail, + getUserVerifiedEmails, + getUserPrimaryEmail, + getUserPrimaryEmailObject +} from "../crud/email"; import { Locals } from "../interfaces/general"; import { createEvent } from "../crud/event"; @@ -55,6 +62,18 @@ export const deleteEmailFromUser = async ( const email = await getEmail(emailId); if (email.userId != userId) throw new Error(ErrorCode.INSUFFICIENT_PERMISSION); + const verifiedEmails = await getUserVerifiedEmails(userId); + if (verifiedEmails.length > 1) { + const currentPrimaryEmailId = (await getUserPrimaryEmailObject(userId)).id; + if (currentPrimaryEmailId == emailId) { + const nextVerifiedEmail = verifiedEmails.filter( + emailObject => emailObject.id != emailId + )[0]; + await updateUser(userId, { primaryEmail: nextVerifiedEmail }); + } + } else { + throw new Error(EventType.EMAIL_CANNOT_DELETE); + } await deleteEmail(emailId); await createEvent( { userId, type: EventType.EMAIL_DELETED, data: { email: email.email } }, diff --git a/src/routes/emails.ts b/src/routes/emails.ts index b0b755b0f..539ec60de 100644 --- a/src/routes/emails.ts +++ b/src/routes/emails.ts @@ -2,6 +2,7 @@ import { Request, Response } from "express"; import { verifyEmail } from "../rest/auth"; import { ErrorCode } from "../interfaces/enum"; import { addEmailToUser, deleteEmailFromUser } from "../rest/user"; +import { resendEmailVerification } from "../crud/email"; export const routeEmailAdd = async (req: Request, res: Response) => { const email = req.body.email; @@ -21,3 +22,10 @@ export const routeEmailVerify = async (req: Request, res: Response) => { await verifyEmail(req.body.token || req.params.token, res.locals); res.json({ success: true }); }; + +export const routeEmailVerifyResend = async (req: Request, res: Response) => { + const emailId = req.params.id || req.body.id; + if (!emailId) throw new Error(ErrorCode.MISSING_FIELD); + await resendEmailVerification(emailId); + res.json({ success: true }); +}; diff --git a/src/routes/index.ts b/src/routes/index.ts index 28ff7f992..94493ddfc 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,8 +1,16 @@ import { Application } from "express"; import asyncHandler from "express-async-handler"; import { routeUserPut, routeUserId } from "./users"; -import { routeEmailVerify, routeEmailAdd, routeEmailDelete } from "./emails"; -import { routeOrganizationCreate, routeOrganizationUpdate } from "./organizations"; +import { + routeEmailVerify, + routeEmailAdd, + routeEmailDelete, + routeEmailVerifyResend +} from "./emails"; +import { + routeOrganizationCreate, + routeOrganizationUpdate +} from "./organizations"; import { authHandler } from "../helpers/middleware"; import { routeAuthVerifyToken, @@ -43,10 +51,15 @@ const routesUser = (app: Application) => { const routesEmail = (app: Application) => { app.put("/emails", authHandler, asyncHandler(routeEmailAdd)); app.delete("/emails/:id", authHandler, asyncHandler(routeEmailDelete)); + app.post("/emails/:id/resend", asyncHandler(routeEmailVerifyResend)); app.post("/emails/verify", asyncHandler(routeEmailVerify)); }; const routesOrganization = (app: Application) => { app.put("/organizations", authHandler, asyncHandler(routeOrganizationCreate)); - app.patch("/organizations/:id", authHandler, asyncHandler(routeOrganizationUpdate)); + app.patch( + "/organizations/:id", + authHandler, + asyncHandler(routeOrganizationUpdate) + ); };