change email
parent
5bbcab9fb2
commit
351d14c2fc
|
@ -40,6 +40,7 @@ import fs from "fs-extra";
|
|||
import rabbitmq from "../rabbitmq/rabbitmq";
|
||||
import verifyCaptcha from "../utils/recaptcha";
|
||||
import EmailVerification from "../models/emailVerification";
|
||||
import UserPendingEmailChange from "../models/userPendingEmailChange";
|
||||
|
||||
export async function SignUp(req: Request, res: Response) {
|
||||
try {
|
||||
|
@ -63,7 +64,7 @@ export async function SignUp(req: Request, res: Response) {
|
|||
!language ||
|
||||
!isCompanyNameValid(companyName) ||
|
||||
!isUsernameValid(username) ||
|
||||
!isEmailValid(email) ||
|
||||
!(await isEmailValid(email)) ||
|
||||
!isLanguageCodeValid(language) ||
|
||||
rememberMe === undefined ||
|
||||
!recaptcha
|
||||
|
@ -135,10 +136,7 @@ export async function SignUp(req: Request, res: Response) {
|
|||
});
|
||||
|
||||
rabbitmq.sendEmail(email, "dashboardSignUpEmailVerification", language, {
|
||||
emailVerificationUrl: getEmailVerificationUrl(
|
||||
String(state),
|
||||
emailVerificationId
|
||||
),
|
||||
emailVerificationUrl: getEmailVerificationUrl(state, emailVerificationId),
|
||||
});
|
||||
|
||||
// create user
|
||||
|
@ -175,7 +173,7 @@ export async function Login(req: Request, res: Response) {
|
|||
|
||||
email = email.toLowerCase();
|
||||
|
||||
if (!isEmailValid(email)) {
|
||||
if (!(await isEmailValid(email, false))) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
|
@ -423,7 +421,7 @@ export async function IsEmailAvailable(req: Request, res: Response) {
|
|||
|
||||
email = email.toLowerCase();
|
||||
|
||||
if (!isEmailValid(email)) {
|
||||
if (!(await isEmailValid(email))) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
|
@ -470,9 +468,9 @@ export async function GetUserProfileSettings(req: Request, res: Response) {
|
|||
|
||||
export async function UpdateUserProfileSettings(req: Request, res: Response) {
|
||||
try {
|
||||
const { language, analyticsEnabled, username, email } = req.body;
|
||||
const { language, analyticsEnabled, username } = req.body;
|
||||
|
||||
if (!language && analyticsEnabled === undefined && !username && !email) {
|
||||
if (!language && analyticsEnabled === undefined && !username) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
|
@ -508,14 +506,6 @@ export async function UpdateUserProfileSettings(req: Request, res: Response) {
|
|||
user.username = username;
|
||||
}
|
||||
|
||||
if (email) {
|
||||
if (!isEmailValid(email)) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
user.email = email;
|
||||
}
|
||||
|
||||
await user.save();
|
||||
|
||||
res.status(200).send({ msg: "user profile settings updated" });
|
||||
|
@ -740,7 +730,7 @@ export async function ExportUserAccount(req: Request, res: Response) {
|
|||
if (
|
||||
!email ||
|
||||
!password ||
|
||||
!isEmailValid(email) ||
|
||||
!(await isEmailValid(email)) ||
|
||||
!isPasswordValid(password)
|
||||
) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
|
@ -882,6 +872,85 @@ export async function VerifyEmail(req: Request, res: Response) {
|
|||
|
||||
res.status(200).send({ msg: "email verified" });
|
||||
return;
|
||||
} else if (
|
||||
emailVerification.state ===
|
||||
EMAIL_VERIFICATION_STATE.PENDING_USER_PROFILE_EMAIL_CHANGE_VERIFICATION
|
||||
) {
|
||||
const { password } = req.body;
|
||||
|
||||
if (!password) {
|
||||
return res.status(200).send({ status: "actionRequired" });
|
||||
}
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
user_id: emailVerification.user_id,
|
||||
},
|
||||
attributes: ["password"],
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
const decodedPassword = decodeBase64(password);
|
||||
|
||||
const match = await matchPassword(decodedPassword, user.password);
|
||||
|
||||
if (!match) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
// update user email
|
||||
|
||||
const userPendingEmailChange = await UserPendingEmailChange.findOne({
|
||||
where: {
|
||||
email_verification_id: emailVerificationId,
|
||||
},
|
||||
attributes: ["new_email"],
|
||||
});
|
||||
|
||||
if (!userPendingEmailChange) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
// update user email
|
||||
|
||||
await User.update(
|
||||
{
|
||||
email: userPendingEmailChange.new_email,
|
||||
},
|
||||
{
|
||||
where: {
|
||||
user_id: emailVerification.user_id,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// delete email verification and user pending email change
|
||||
|
||||
await EmailVerification.destroy({
|
||||
where: {
|
||||
email_verification_id: emailVerificationId,
|
||||
},
|
||||
});
|
||||
|
||||
await UserPendingEmailChange.destroy({
|
||||
where: {
|
||||
email_verification_id: emailVerificationId,
|
||||
},
|
||||
});
|
||||
|
||||
// delete all sessions of this user by deleting all sessions with this user_id
|
||||
|
||||
await Session.destroy({
|
||||
where: {
|
||||
user_id: emailVerification.user_id,
|
||||
},
|
||||
});
|
||||
|
||||
res.status(200).send({ msg: "email changed" });
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(400).send({ err: "invalid request" });
|
||||
|
@ -890,3 +959,65 @@ export async function VerifyEmail(req: Request, res: Response) {
|
|||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
||||
|
||||
export async function UpdateUserProfileEmail(req: Request, res: Response) {
|
||||
try {
|
||||
let { email } = req.body;
|
||||
|
||||
if (!email || !(await isEmailValid(email))) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
email = email.toLowerCase();
|
||||
|
||||
const session = await getUserSession(req);
|
||||
|
||||
if (!session) {
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
user_id: session.user_id,
|
||||
},
|
||||
attributes: ["language"],
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
const emailVerificationId = newEmailVerificationId();
|
||||
const state =
|
||||
EMAIL_VERIFICATION_STATE.PENDING_USER_PROFILE_EMAIL_CHANGE_VERIFICATION;
|
||||
|
||||
await EmailVerification.create({
|
||||
email_verification_id: emailVerificationId,
|
||||
user_id: session.user_id,
|
||||
state: state,
|
||||
});
|
||||
|
||||
await UserPendingEmailChange.create({
|
||||
email_verification_id: emailVerificationId,
|
||||
user_id: session.user_id,
|
||||
new_email: email,
|
||||
});
|
||||
|
||||
rabbitmq.sendEmail(
|
||||
email,
|
||||
"dashboardUserProfileChangeToNewEmailVerification",
|
||||
user.language,
|
||||
{
|
||||
emailVerificationUrl: getEmailVerificationUrl(
|
||||
state,
|
||||
emailVerificationId
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
res.status(200).send({ msg: "user email updated" });
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import StoreService from "./storeService";
|
|||
import StoreServiceActivity from "./storeServiceActivity";
|
||||
import StoreServiceActivityUsers from "./storeServiceActivityUsers";
|
||||
import User from "./user";
|
||||
import UserPendingEmailChange from "./userPendingEmailChange";
|
||||
|
||||
function syncModels() {
|
||||
EmailVerification.sync();
|
||||
|
@ -16,6 +17,7 @@ function syncModels() {
|
|||
StoreServiceActivity.sync();
|
||||
StoreServiceActivityUsers.sync();
|
||||
Feedback.sync();
|
||||
UserPendingEmailChange.sync();
|
||||
|
||||
// UserGoogleTokens.sync(); not needed as it is created by the calendar backend
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import { DataTypes, Model } from "sequelize";
|
||||
import sequelize from "../database/database";
|
||||
|
||||
interface UserPendingEmailChangeAttributes {
|
||||
email_verification_id: string; // code that is sent to the user's email
|
||||
user_id: string;
|
||||
new_email: string;
|
||||
}
|
||||
|
||||
class UserPendingEmailChange
|
||||
extends Model<UserPendingEmailChangeAttributes>
|
||||
implements UserPendingEmailChangeAttributes
|
||||
{
|
||||
declare email_verification_id: string;
|
||||
declare user_id: string;
|
||||
declare new_email: string;
|
||||
}
|
||||
|
||||
UserPendingEmailChange.init(
|
||||
{
|
||||
// Model attributes are defined here
|
||||
email_verification_id: {
|
||||
primaryKey: true,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
user_id: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
new_email: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
tableName: "user_pending_email_change",
|
||||
sequelize, // passing the `sequelize` instance is required
|
||||
createdAt: "created_at",
|
||||
updatedAt: "updated_at",
|
||||
}
|
||||
);
|
||||
|
||||
export default UserPendingEmailChange;
|
|
@ -24,6 +24,11 @@ router.post(
|
|||
sessionProtection,
|
||||
userController.UpdateUserProfilePassword
|
||||
);
|
||||
router.post(
|
||||
"/profile/email",
|
||||
sessionProtection,
|
||||
userController.UpdateUserProfileEmail
|
||||
);
|
||||
router.get(
|
||||
"/profile/sessions",
|
||||
sessionProtection,
|
||||
|
@ -45,6 +50,6 @@ router.post(
|
|||
userController.ExportUserAccount
|
||||
);
|
||||
router.get("/profile/export/:id", userController.GetExportedUserAccount);
|
||||
router.get("/verify/:state/:emailVerificationId", userController.VerifyEmail);
|
||||
router.post("/verify/:state/:emailVerificationId", userController.VerifyEmail);
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -74,6 +74,7 @@ export const ACCOUNT_DEMO_DAYS = Number(
|
|||
|
||||
export enum EMAIL_VERIFICATION_STATE {
|
||||
PENDING_EMAIL_VERIFICATION = 0, // account is created but email is not verified yet
|
||||
PENDING_USER_PROFILE_EMAIL_CHANGE_VERIFICATION = 1, // user wants to change email, new email is not verified yet
|
||||
}
|
||||
|
||||
export const DASHBOARD_URL = process.env.DASHBOARD_URL as string;
|
||||
|
|
|
@ -110,8 +110,8 @@ export async function getUserSession(req: Request) {
|
|||
}
|
||||
|
||||
export function getEmailVerificationUrl(
|
||||
state: string,
|
||||
state: number,
|
||||
emailVerificationId: string
|
||||
) {
|
||||
return `${DASHBOARD_URL}/verify/${state}/${emailVerificationId}`;
|
||||
return `${DASHBOARD_URL}/verify/${String(state)}/${emailVerificationId}`;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
CALENDAR_MAX_EARLIEST_BOOKING_TIME,
|
||||
} from "../utils/constants";
|
||||
import User from "../models/user";
|
||||
import UserPendingEmailChange from "../models/userPendingEmailChange";
|
||||
|
||||
// TODO: regex for username
|
||||
export function isUsernameValid(username: string) {
|
||||
|
@ -35,8 +36,10 @@ export function isUsernameValid(username: string) {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO: regex for email
|
||||
export async function isEmailValid(email: string) {
|
||||
export async function isEmailValid(
|
||||
email: string,
|
||||
checkDatabase: boolean = true
|
||||
) {
|
||||
if (
|
||||
email.length < EMAIL_MIN_LENGTH ||
|
||||
email.length > EMAIL_MAX_LENGTH ||
|
||||
|
@ -45,19 +48,42 @@ export async function isEmailValid(email: string) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// check if email is already taken in the database
|
||||
|
||||
const existingUser = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
return false;
|
||||
if (!checkDatabase) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
try {
|
||||
// check if email is already taken in the database
|
||||
|
||||
const existingUser = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingUser !== null) {
|
||||
console.log("existingUser");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if email is already taken in the pending email change table
|
||||
|
||||
const existingPendingEmailChange = await UserPendingEmailChange.findOne({
|
||||
where: {
|
||||
new_email: email,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingPendingEmailChange !== null) {
|
||||
console.log("existingPendingEmailChange");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: regex for password
|
||||
|
|
Loading…
Reference in New Issue