change email
parent
5bbcab9fb2
commit
351d14c2fc
|
@ -40,6 +40,7 @@ import fs from "fs-extra";
|
||||||
import rabbitmq from "../rabbitmq/rabbitmq";
|
import rabbitmq from "../rabbitmq/rabbitmq";
|
||||||
import verifyCaptcha from "../utils/recaptcha";
|
import verifyCaptcha from "../utils/recaptcha";
|
||||||
import EmailVerification from "../models/emailVerification";
|
import EmailVerification from "../models/emailVerification";
|
||||||
|
import UserPendingEmailChange from "../models/userPendingEmailChange";
|
||||||
|
|
||||||
export async function SignUp(req: Request, res: Response) {
|
export async function SignUp(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
|
@ -63,7 +64,7 @@ export async function SignUp(req: Request, res: Response) {
|
||||||
!language ||
|
!language ||
|
||||||
!isCompanyNameValid(companyName) ||
|
!isCompanyNameValid(companyName) ||
|
||||||
!isUsernameValid(username) ||
|
!isUsernameValid(username) ||
|
||||||
!isEmailValid(email) ||
|
!(await isEmailValid(email)) ||
|
||||||
!isLanguageCodeValid(language) ||
|
!isLanguageCodeValid(language) ||
|
||||||
rememberMe === undefined ||
|
rememberMe === undefined ||
|
||||||
!recaptcha
|
!recaptcha
|
||||||
|
@ -135,10 +136,7 @@ export async function SignUp(req: Request, res: Response) {
|
||||||
});
|
});
|
||||||
|
|
||||||
rabbitmq.sendEmail(email, "dashboardSignUpEmailVerification", language, {
|
rabbitmq.sendEmail(email, "dashboardSignUpEmailVerification", language, {
|
||||||
emailVerificationUrl: getEmailVerificationUrl(
|
emailVerificationUrl: getEmailVerificationUrl(state, emailVerificationId),
|
||||||
String(state),
|
|
||||||
emailVerificationId
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// create user
|
// create user
|
||||||
|
@ -175,7 +173,7 @@ export async function Login(req: Request, res: Response) {
|
||||||
|
|
||||||
email = email.toLowerCase();
|
email = email.toLowerCase();
|
||||||
|
|
||||||
if (!isEmailValid(email)) {
|
if (!(await isEmailValid(email, false))) {
|
||||||
return res.status(400).send({ err: "invalid request" });
|
return res.status(400).send({ err: "invalid request" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,7 +421,7 @@ export async function IsEmailAvailable(req: Request, res: Response) {
|
||||||
|
|
||||||
email = email.toLowerCase();
|
email = email.toLowerCase();
|
||||||
|
|
||||||
if (!isEmailValid(email)) {
|
if (!(await isEmailValid(email))) {
|
||||||
return res.status(400).send({ err: "invalid request" });
|
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) {
|
export async function UpdateUserProfileSettings(req: Request, res: Response) {
|
||||||
try {
|
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" });
|
return res.status(400).send({ err: "invalid request" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,14 +506,6 @@ export async function UpdateUserProfileSettings(req: Request, res: Response) {
|
||||||
user.username = username;
|
user.username = username;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (email) {
|
|
||||||
if (!isEmailValid(email)) {
|
|
||||||
return res.status(400).send({ err: "invalid request" });
|
|
||||||
}
|
|
||||||
|
|
||||||
user.email = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
await user.save();
|
await user.save();
|
||||||
|
|
||||||
res.status(200).send({ msg: "user profile settings updated" });
|
res.status(200).send({ msg: "user profile settings updated" });
|
||||||
|
@ -740,7 +730,7 @@ export async function ExportUserAccount(req: Request, res: Response) {
|
||||||
if (
|
if (
|
||||||
!email ||
|
!email ||
|
||||||
!password ||
|
!password ||
|
||||||
!isEmailValid(email) ||
|
!(await isEmailValid(email)) ||
|
||||||
!isPasswordValid(password)
|
!isPasswordValid(password)
|
||||||
) {
|
) {
|
||||||
return res.status(400).send({ err: "invalid request" });
|
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" });
|
res.status(200).send({ msg: "email verified" });
|
||||||
return;
|
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" });
|
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" });
|
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 StoreServiceActivity from "./storeServiceActivity";
|
||||||
import StoreServiceActivityUsers from "./storeServiceActivityUsers";
|
import StoreServiceActivityUsers from "./storeServiceActivityUsers";
|
||||||
import User from "./user";
|
import User from "./user";
|
||||||
|
import UserPendingEmailChange from "./userPendingEmailChange";
|
||||||
|
|
||||||
function syncModels() {
|
function syncModels() {
|
||||||
EmailVerification.sync();
|
EmailVerification.sync();
|
||||||
|
@ -16,6 +17,7 @@ function syncModels() {
|
||||||
StoreServiceActivity.sync();
|
StoreServiceActivity.sync();
|
||||||
StoreServiceActivityUsers.sync();
|
StoreServiceActivityUsers.sync();
|
||||||
Feedback.sync();
|
Feedback.sync();
|
||||||
|
UserPendingEmailChange.sync();
|
||||||
|
|
||||||
// UserGoogleTokens.sync(); not needed as it is created by the calendar backend
|
// 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,
|
sessionProtection,
|
||||||
userController.UpdateUserProfilePassword
|
userController.UpdateUserProfilePassword
|
||||||
);
|
);
|
||||||
|
router.post(
|
||||||
|
"/profile/email",
|
||||||
|
sessionProtection,
|
||||||
|
userController.UpdateUserProfileEmail
|
||||||
|
);
|
||||||
router.get(
|
router.get(
|
||||||
"/profile/sessions",
|
"/profile/sessions",
|
||||||
sessionProtection,
|
sessionProtection,
|
||||||
|
@ -45,6 +50,6 @@ router.post(
|
||||||
userController.ExportUserAccount
|
userController.ExportUserAccount
|
||||||
);
|
);
|
||||||
router.get("/profile/export/:id", userController.GetExportedUserAccount);
|
router.get("/profile/export/:id", userController.GetExportedUserAccount);
|
||||||
router.get("/verify/:state/:emailVerificationId", userController.VerifyEmail);
|
router.post("/verify/:state/:emailVerificationId", userController.VerifyEmail);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -74,6 +74,7 @@ export const ACCOUNT_DEMO_DAYS = Number(
|
||||||
|
|
||||||
export enum EMAIL_VERIFICATION_STATE {
|
export enum EMAIL_VERIFICATION_STATE {
|
||||||
PENDING_EMAIL_VERIFICATION = 0, // account is created but email is not verified yet
|
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;
|
export const DASHBOARD_URL = process.env.DASHBOARD_URL as string;
|
||||||
|
|
|
@ -110,8 +110,8 @@ export async function getUserSession(req: Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEmailVerificationUrl(
|
export function getEmailVerificationUrl(
|
||||||
state: string,
|
state: number,
|
||||||
emailVerificationId: string
|
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,
|
CALENDAR_MAX_EARLIEST_BOOKING_TIME,
|
||||||
} from "../utils/constants";
|
} from "../utils/constants";
|
||||||
import User from "../models/user";
|
import User from "../models/user";
|
||||||
|
import UserPendingEmailChange from "../models/userPendingEmailChange";
|
||||||
|
|
||||||
// TODO: regex for username
|
// TODO: regex for username
|
||||||
export function isUsernameValid(username: string) {
|
export function isUsernameValid(username: string) {
|
||||||
|
@ -35,8 +36,10 @@ export function isUsernameValid(username: string) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: regex for email
|
export async function isEmailValid(
|
||||||
export async function isEmailValid(email: string) {
|
email: string,
|
||||||
|
checkDatabase: boolean = true
|
||||||
|
) {
|
||||||
if (
|
if (
|
||||||
email.length < EMAIL_MIN_LENGTH ||
|
email.length < EMAIL_MIN_LENGTH ||
|
||||||
email.length > EMAIL_MAX_LENGTH ||
|
email.length > EMAIL_MAX_LENGTH ||
|
||||||
|
@ -45,19 +48,42 @@ export async function isEmailValid(email: string) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if email is already taken in the database
|
if (!checkDatabase) {
|
||||||
|
return true;
|
||||||
const existingUser = await User.findOne({
|
|
||||||
where: {
|
|
||||||
email: email,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingUser) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// TODO: regex for password
|
||||||
|
|
Loading…
Reference in New Issue