diff --git a/env.example b/env.example index b302c18..a186b00 100644 --- a/env.example +++ b/env.example @@ -7,27 +7,22 @@ MARIADB_DATABASE=terminplanner MARIADB_USER=terminplanner MARIADB_PASSWORD=your_password +DASHBOARD_URL= +DASHBOARD_API_URL= + GOOGLE_CLIENT_ID=your_client_id GOOGLE_CLIENT_SECRET=your_client_secret GOOGLE_CALLBACK_URL=your_callback_url -PASSPORT_FAILURE_REDIRECT_URL=your_failure_redirect_url -PASSPORT_SUCCESS_REDIRECT_URL=your_success_redirect_url - TERMIN_PLANNER_AUTHORIZATION_PASSWORD=your_authorization_password TERMIN_PLANNER_URL=your_termin_planner_url -WEBSITE_BUILDER_TEMPLATE_REPOSITORY_URL=https://your-git-repo.com/website-template.git -WEBSITE_BUILDER_TMP_DIR=./tmp -WEBSITE_BUILDER_TMP_DIR_WEBSITE_TEMPLATE=./tmp/website-template -WEBSITE_BUILDER_TMP_CUSTOMER_WEBSITES_DIR=./customer-websites - RABBITMQ_HOST= RABBITMQ_PORT= RABBITMQ_USERNAME= RABBITMQ_PASSWORD= RABBITMQ_MAIL_QUEUE= -ACCOUNT_EXPORT_URL= +RECAPTCHA_SECRET_KEY= -RECAPTCHA_SECRET_KEY= \ No newline at end of file +CONSTANTS_ACCOUNT_DEMO_DAYS= \ No newline at end of file diff --git a/server.ts b/server.ts index 61d9c81..8d51103 100644 --- a/server.ts +++ b/server.ts @@ -7,14 +7,13 @@ import GoogleStrategy from "passport-google-oauth20"; import cookieParser from "cookie-parser"; import session from "express-session"; import useragent from "express-useragent"; -import rabbitmq from "./src/rabbitmq/rabbitmq"; import calendarRoutes from "./src/routes/calendarRoutes"; import storeRoutes from "./src/routes/storeRoutes"; import storeServicesRoutes from "./src/routes/storeServicesRoutes"; import userRoutes from "./src/routes/userRoutes"; import usersRoutes from "./src/routes/usersRoutes"; -import websiteRoutes from "./src/routes/websiteRoutes"; +//import websiteRoutes from "./src/routes/websiteRoutes"; dotenv.config(); @@ -22,8 +21,9 @@ import swaggerJsDoc from "swagger-jsdoc"; import syncModels from "./src/models/index"; import logger from "./src/logger/logger"; import passport from "passport"; -import fs from "fs-extra"; -import { cloneWebsiteTemplate } from "./src/controllers/websiteController"; +import rabbitmq from "./src/rabbitmq/rabbitmq"; +import { GOOGLE_CALLBACK_URL } from "./src/utils/constants"; +//import { cloneWebsiteTemplate } from "./src/controllers/websiteController"; const app: Express = express(); const host = process.env.HOST || "localhost"; const port = Number(process.env.PORT) || 3000; @@ -68,7 +68,7 @@ passport.use( { clientID: process.env.GOOGLE_CLIENT_ID as string, clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, - callbackURL: process.env.GOOGLE_CALLBACK_URL as string, + callbackURL: GOOGLE_CALLBACK_URL, /*scope: [ "https://www.googleapis.com/auth/calendar.app.created", "https://www.googleapis.com/auth/calendar.events.freebusy", @@ -117,7 +117,7 @@ app.use("/api/v1/store", storeRoutes); app.use("/api/v1/store/services", storeServicesRoutes); app.use("/api/v1/user", userRoutes); app.use("/api/v1/users", usersRoutes); -app.use("/api/v1/website", websiteRoutes); +//app.use("/api/v1/website", websiteRoutes); const specs = swaggerJsDoc(options); app.use("/api-docs", swaggerUI.serve, swaggerUI.setup(specs)); @@ -135,7 +135,7 @@ app.use((err: any, req: any, res: any, next: any) => { syncModels(); // create tmp folder if not exists - +/* [ process.env.WEBSITE_BUILDER_TMP_DIR as string, process.env.WEBSITE_BUILDER_TMP_CUSTOMER_WEBSITES_DIR as string, @@ -154,16 +154,21 @@ if ( ) { cloneWebsiteTemplate(); } - +*/ // start server -rabbitmq( - process.env.RABBITMQ_USERNAME as string, - process.env.RABBITMQ_PASSWORD as string, - process.env.RABBITMQ_HOST as string, - process.env.RABBITMQ_PORT as string -); - -app.listen(port, host, () => - logger.info(`⚡️[server]: Server is running at http://${host}:${port}`) -); +rabbitmq + .connect( + process.env.RABBITMQ_USERNAME as string, + process.env.RABBITMQ_PASSWORD as string, + process.env.RABBITMQ_HOST as string, + process.env.RABBITMQ_PORT as string + ) + .then(() => { + app.listen(port, host, () => + logger.info(`⚡️[server]: Server is running at http://${host}:${port}`) + ); + }) + .catch((err) => { + logger.error(err); + }); diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index bb961e5..1a73f04 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -2,7 +2,6 @@ import { Request, Response } from "express"; import logger from "../logger/logger"; import User from "../models/user"; import { - isAccountNameValid, isCompanyNameValid, isEmailValid, isLanguageCodeValid, @@ -15,15 +14,19 @@ import { CALENDAR_MAX_FUTURE_BOOKING_DAYS, CALENDAR_MAX_SERVICE_DURATION, CALENDAR_MIN_EARLIEST_BOOKING_TIME, + DASHBOARD_URL, + EMAIL_VERIFICATION_STATE, Roles, USER_ANALYTICS_ENABLED_DEFAULT, } from "../utils/constants"; import { decodeBase64, + getEmailVerificationUrl, getUserSession, hashPassword, matchPassword, newAccountExportId, + newEmailVerificationId, newFeedbackId, newStoreId, newUserId, @@ -33,15 +36,16 @@ import Store from "../models/store"; import Session from "../models/session"; import Feedback from "../models/feedback"; import fs from "fs-extra"; -import { channel } from "../rabbitmq/rabbitmq"; +import rabbitmq from "../rabbitmq/rabbitmq"; import verifyCaptcha from "../utils/recaptcha"; +import EmailVerification from "../models/emailVerification"; export async function SignUp(req: Request, res: Response) { try { let { companyName, username, - accountName, + email, password, language, rememberMe, @@ -53,12 +57,12 @@ export async function SignUp(req: Request, res: Response) { if ( !companyName || !username || - !accountName || + !email || !password || !language || !isCompanyNameValid(companyName) || !isUsernameValid(username) || - !isAccountNameValid(accountName) || + !isEmailValid(email) || !isLanguageCodeValid(language) || rememberMe === undefined || !recaptcha @@ -77,13 +81,13 @@ export async function SignUp(req: Request, res: Response) { return res.status(400).send({ err: "invalid request" }); } - accountName = accountName.toLowerCase(); + email = email.toLowerCase(); // check if user already exists const existingUser = await User.findOne({ where: { - account_name: accountName, + email: email, }, }); @@ -107,7 +111,7 @@ export async function SignUp(req: Request, res: Response) { let userId = newUserId(); - Store.create({ + const store = await Store.create({ store_id: newStoreId(), owner_user_id: userId, name: companyName, @@ -115,34 +119,41 @@ export async function SignUp(req: Request, res: Response) { calendar_min_earliest_booking_time: CALENDAR_MIN_EARLIEST_BOOKING_TIME, calendar_max_service_duration: CALENDAR_MAX_SERVICE_DURATION, address: "", - }) - .then(async (store) => { - // create user + }); - await User.create({ - user_id: userId, - store_id: store.store_id, - role: Roles.Master, - account_name: accountName, - username: username, - password: hashedPassword, - language: language, - analytics_enabled: USER_ANALYTICS_ENABLED_DEFAULT, - state: ACCOUNT_STATE.ACTIVE, - }) - .then((user) => { - // create session - saveSession(req, res, user.user_id, user.username, rememberMe); - }) - .catch((err) => { - logger.error(err); - res.status(500).send({ err: "invalid request" }); - }); - }) - .catch((err) => { - logger.error(err); - res.status(500).send({ err: "invalid request" }); - }); + // create email verification + + const emailVerificationId = newEmailVerificationId(); + const state = EMAIL_VERIFICATION_STATE.PENDING_EMAIL_VERIFICATION; + + await EmailVerification.create({ + email_verification_id: emailVerificationId, + user_id: userId, + state: state, + }); + + rabbitmq.sendEmail(email, "dashboardSignUpEmailVerification", language, { + emailVerificationUrl: getEmailVerificationUrl( + String(state), + emailVerificationId + ), + }); + + // create user + + const user = await User.create({ + user_id: userId, + store_id: store.store_id, + role: Roles.Master, + email: email, + username: username, + password: hashedPassword, + language: language, + analytics_enabled: USER_ANALYTICS_ENABLED_DEFAULT, + state: ACCOUNT_STATE.PENDING_EMAIL_VERIFICATION, + }); + + saveSession(req, res, false, user.user_id, user.username, rememberMe); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); @@ -151,24 +162,24 @@ export async function SignUp(req: Request, res: Response) { export async function Login(req: Request, res: Response) { try { - let { accountName, password, rememberMe, recaptcha } = req.body; + let { email, password, rememberMe, recaptcha } = req.body; // validate request - if (!accountName) { + if (!email) { return res.status(400).send({ err: "invalid request" }); } - accountName = accountName.toLowerCase(); + email = email.toLowerCase(); - if (!isAccountNameValid(accountName)) { + if (!isEmailValid(email)) { return res.status(400).send({ err: "invalid request" }); } // check if user exists const user = await User.findOne({ where: { - account_name: accountName, + email: email, }, attributes: ["user_id", "password", "state"], }); @@ -179,7 +190,7 @@ export async function Login(req: Request, res: Response) { // if password not provided, then send user state // user is on the login page on the first step of the login process - // and only needs to enter their account name to get the user state to know what to do next + // and only needs to enter their email to get the user state to know what to do next if (password === undefined) { return res.status(200).send({ state: user.state }); @@ -244,7 +255,7 @@ export async function Login(req: Request, res: Response) { } // create session - saveSession(req, res, user.user_id, user.username, rememberMe); + saveSession(req, res, true, user.user_id, user.username, rememberMe); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); @@ -385,19 +396,19 @@ export async function GetUser(req: Request, res: Response) { } } -export async function IsAccountNameAvailable(req: Request, res: Response) { +export async function IsEmailAvailable(req: Request, res: Response) { try { - let { accountName } = req.body; + let { email } = req.body; // validate request - if (!accountName) { + if (!email) { return res.status(400).send({ err: "invalid request" }); } - accountName = accountName.toLowerCase(); + email = email.toLowerCase(); - if (!isAccountNameValid(accountName)) { + if (!isEmailValid(email)) { return res.status(400).send({ err: "invalid request" }); } @@ -405,7 +416,7 @@ export async function IsAccountNameAvailable(req: Request, res: Response) { const user = await User.findOne({ where: { - account_name: accountName, + email: email, }, }); @@ -413,7 +424,7 @@ export async function IsAccountNameAvailable(req: Request, res: Response) { return res.status(400).send({ err: "invalid request" }); } - res.status(200).send({ msg: "account name available" }); + res.status(200).send({ msg: "email available" }); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); @@ -432,7 +443,7 @@ export async function GetUserProfileSettings(req: Request, res: Response) { where: { user_id: session.user_id, }, - attributes: ["language", "analytics_enabled", "username", "account_name"], + attributes: ["language", "analytics_enabled", "username", "email"], }); res.status(200).json(user); @@ -444,14 +455,9 @@ export async function GetUserProfileSettings(req: Request, res: Response) { export async function UpdateUserProfileSettings(req: Request, res: Response) { try { - const { language, analyticsEnabled, username, accountName } = req.body; + const { language, analyticsEnabled, username, email } = req.body; - if ( - !language && - analyticsEnabled === undefined && - !username && - !accountName - ) { + if (!language && analyticsEnabled === undefined && !username && !email) { return res.status(400).send({ err: "invalid request" }); } @@ -487,12 +493,12 @@ export async function UpdateUserProfileSettings(req: Request, res: Response) { user.username = username; } - if (accountName) { - if (!isAccountNameValid(accountName)) { + if (email) { + if (!isEmailValid(email)) { return res.status(400).send({ err: "invalid request" }); } - user.account_name = accountName; + user.email = email; } await user.save(); @@ -757,7 +763,7 @@ export async function ExportUserAccount(req: Request, res: Response) { user: { user_id: user.user_id, username: user.username, - account_name: user.account_name, + email: user.email, calendar_max_future_booking_days: user.calendar_max_future_booking_days, calendar_min_earliest_booking_time: @@ -778,22 +784,11 @@ export async function ExportUserAccount(req: Request, res: Response) { // send email with file - channel.sendToQueue( - process.env.RABBITMQ_MAIL_QUEUE as string, - Buffer.from( - JSON.stringify({ - m: email, // UserMail - t: "dashboardUserAccountExportFinish", // TemplateId - l: "de", // LanguageId - // BodyData - b: { - accountExportDownloadUrl: `${ - process.env.ACCOUNT_EXPORT_URL as string - }${accountExportId}`, - }, - }) - ) - ); + rabbitmq.sendEmail(email, "dashboardUserAccountExportFinish", "de", { + accountExportDownloadUrl: `${ + process.env.ACCOUNT_EXPORT_URL as string + }${accountExportId}`, + }); } catch (error) { logger.error(error); } @@ -806,7 +801,7 @@ export async function ExportUserAccount(req: Request, res: Response) { } } -export function GetExportedUserAccount(req: Request, res: Response) { +export async function GetExportedUserAccount(req: Request, res: Response) { try { const { id } = req.params; @@ -830,3 +825,53 @@ export function GetExportedUserAccount(req: Request, res: Response) { res.status(500).send({ err: "invalid request" }); } } + +export async function VerifyEmail(req: Request, res: Response) { + try { + const { state, emailVerificationId } = req.params; + + if (state === undefined || !emailVerificationId) { + return res.status(400).send({ err: "invalid request" }); + } + + const emailVerification = await EmailVerification.findOne({ + where: { + email_verification_id: emailVerificationId, + }, + }); + + if (!emailVerification) { + return res.status(400).send({ err: "invalid request" }); + } + + if ( + emailVerification.state === + EMAIL_VERIFICATION_STATE.PENDING_EMAIL_VERIFICATION + ) { + await User.update( + { + state: ACCOUNT_STATE.ACTIVE, + }, + { + where: { + user_id: emailVerification.user_id, + }, + } + ); + + await EmailVerification.destroy({ + where: { + email_verification_id: emailVerificationId, + }, + }); + + res.status(200).send({ msg: "email verified" }); + return; + } + + res.status(400).send({ err: "invalid request" }); + } catch (error) { + logger.error(error); + res.status(500).send({ err: "invalid request" }); + } +} diff --git a/src/controllers/usersController.ts b/src/controllers/usersController.ts index 198f5ee..6f871e7 100644 --- a/src/controllers/usersController.ts +++ b/src/controllers/usersController.ts @@ -1,7 +1,7 @@ import { Request, Response } from "express"; import logger from "../logger/logger"; import { - isAccountNameValid, + isEmailValid, isLanguageCodeValid, isPasswordValid, isUserIdValid, @@ -27,7 +27,7 @@ export async function AddEmployee(req: Request, res: Response) { let { storeId, username, - accountName, + email, password, calendarMaxFutureBookingDays, calendarMinEarliestBookingTime, @@ -40,18 +40,15 @@ export async function AddEmployee(req: Request, res: Response) { if ( !storeId || !username || - !accountName || + !email || passwordSetOnInitLogging === undefined || (!password && passwordSetOnInitLogging === false) || !language || !isLanguageCodeValid(language) ) { - logger.debug("Invalid request"); return res.status(400).send({ err: "invalid request" }); } - logger.debug("Request is valid %s %s", passwordSetOnInitLogging, password); - // verify if requester is a store master const requesterSession = await getUserSession(req); @@ -74,14 +71,11 @@ export async function AddEmployee(req: Request, res: Response) { return res.status(401).send({ err: "unauthorized" }); } - // validate username and account name + // validate username and email - accountName = accountName.toLowerCase(); + email = email.toLowerCase(); - if ( - !isUsernameValid(username) || - !(await isAccountNameValid(accountName)) - ) { + if (!isUsernameValid(username) || !(await isEmailValid(email))) { return res.status(400).send({ err: "invalid request" }); } @@ -89,7 +83,7 @@ export async function AddEmployee(req: Request, res: Response) { const existingUser = await User.findOne({ where: { - account_name: accountName, + email: email, }, }); @@ -105,7 +99,7 @@ export async function AddEmployee(req: Request, res: Response) { user_id: userId, store_id: storeId, role: Roles.Worker, - account_name: accountName, + email: email, username: username, calendar_max_future_booking_days: calendarMaxFutureBookingDays, calendar_min_earliest_booking_time: calendarMinEarliestBookingTime, @@ -136,7 +130,7 @@ export async function AddEmployee(req: Request, res: Response) { user_id: string; store_id: any; role: string; - account_name: any; + email: any; username: any; calendar_max_future_booking_days: any; calendar_min_earliest_booking_time: any; @@ -193,7 +187,7 @@ export async function GetEmployees(req: Request, res: Response) { return res.status(401).send({ err: "unauthorized" }); } - // find all employees of the requester and select only the username and account name + // find all employees of the requester const employees = await User.findAll({ where: { @@ -202,7 +196,7 @@ export async function GetEmployees(req: Request, res: Response) { attributes: [ "user_id", "username", - "account_name", + "email", "calendar_max_future_booking_days", "calendar_min_earliest_booking_time", ], @@ -234,13 +228,11 @@ export async function UpdateEmployee(req: Request, res: Response) { let { userId, username, - accountName, + email, calendarMaxFutureBookingDays, calendarMinEarliestBookingTime, } = req.body; - // validate username and account name - if (!isUserIdValid(userId)) { return res.status(400).send({ err: "invalid request" }); } @@ -282,17 +274,17 @@ export async function UpdateEmployee(req: Request, res: Response) { let update = {}; - if (accountName) { - accountName = accountName.toLowerCase(); + if (email) { + email = email.toLowerCase(); - if (!(await isAccountNameValid(accountName))) { + if (!(await isEmailValid(email))) { res.status(400).send({ err: "invalid request" }); return; } update = { ...update, - account_name: accountName, + email: email, }; } diff --git a/src/controllers/websiteController.ts b/src/controllers/websiteController.ts index 42ee74c..244e66b 100644 --- a/src/controllers/websiteController.ts +++ b/src/controllers/websiteController.ts @@ -1,4 +1,4 @@ -import { Request, Response } from "express"; +/*import { Request, Response } from "express"; import logger from "../logger/logger"; import util from "util"; import { exec } from "child_process"; @@ -159,3 +159,4 @@ export async function GetWebsite(req: Request, res: Response) { res.status(500).send({ error: "invalid request" }); } } +*/ diff --git a/src/models/emailVerification.ts b/src/models/emailVerification.ts new file mode 100644 index 0000000..6a25723 --- /dev/null +++ b/src/models/emailVerification.ts @@ -0,0 +1,44 @@ +import { DataTypes, Model } from "sequelize"; +import sequelize from "../database/database"; + +interface EmailVerificationAttributes { + email_verification_id: string; // code that is sent to the user's email + user_id: string; + state: number; // to know for what the code is used for (like password reset, email verification, etc) +} + +class EmailVerification + extends Model + implements EmailVerificationAttributes +{ + declare email_verification_id: string; + declare user_id: string; + declare state: number; +} + +EmailVerification.init( + { + // Model attributes are defined here + email_verification_id: { + primaryKey: true, + type: DataTypes.STRING, + allowNull: false, + }, + user_id: { + type: DataTypes.STRING, + allowNull: false, + }, + state: { + type: DataTypes.TINYINT, + allowNull: false, + }, + }, + { + tableName: "email_verification", + sequelize, // passing the `sequelize` instance is required + createdAt: "created_at", + updatedAt: "updated_at", + } +); + +export default EmailVerification; diff --git a/src/models/index.ts b/src/models/index.ts index 3633254..782474a 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,4 +1,5 @@ import Feedback from "./feedback"; +import EmailVerification from "./emailVerification"; import Session from "./session"; import Store from "./store"; import StoreService from "./storeService"; @@ -8,6 +9,7 @@ import User from "./user"; import Website from "./website"; function syncModels() { + EmailVerification.sync(); User.sync(); Session.sync(); Store.sync(); diff --git a/src/models/user.ts b/src/models/user.ts index 700baf6..ad339b4 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -6,7 +6,7 @@ interface UserAttributes { store_id: string; // TODO: change to role_id role: string; - account_name: string; + email: string; username: string; password?: string; // can be null if user is created by store owner - password is set by user when they first login calendar_max_future_booking_days?: number; @@ -21,7 +21,7 @@ class User extends Model implements UserAttributes { declare user_id: string; declare store_id: string; declare role: string; - declare account_name: string; + declare email: string; declare username: string; declare password: string; declare calendar_max_future_booking_days: number; @@ -49,7 +49,7 @@ User.init( type: DataTypes.STRING, allowNull: false, }, - account_name: { + email: { type: DataTypes.STRING, unique: true, }, diff --git a/src/rabbitmq/rabbitmq.ts b/src/rabbitmq/rabbitmq.ts index 4a407ba..0cb8878 100644 --- a/src/rabbitmq/rabbitmq.ts +++ b/src/rabbitmq/rabbitmq.ts @@ -1,6 +1,6 @@ import amqplib from "amqplib"; -export let channel: amqplib.Channel; +let channel: amqplib.Channel; async function connect( RABBITMQ_USERNAME: string, @@ -13,16 +13,45 @@ async function connect( ); if (!open) { - console.log("RabbitMQ connection failed"); + console.log("⚡️[RabbitMQ]: connection failed"); return; } channel = await open.createChannel(); if (!channel) { - console.log("RabbitMQ channel failed"); + console.log("⚡️[RabbitMQ]: channel failed"); return; } } -export default connect; +async function sendEmail( + email: string, + templateId: string, + languageId: string, + bodyData: any +) { + if (!channel) { + console.log("⚡️[RabbitMQ]: channel not available"); + return; + } + + channel.sendToQueue( + process.env.RABBITMQ_MAIL_QUEUE as string, + Buffer.from( + JSON.stringify({ + m: email, + t: templateId, + l: languageId, + b: bodyData, + }) + ) + ); +} + +const rabbitmq = { + connect, + sendEmail, +}; + +export default rabbitmq; diff --git a/src/routes/calendarRoutes.ts b/src/routes/calendarRoutes.ts index 23b8364..a8838c5 100644 --- a/src/routes/calendarRoutes.ts +++ b/src/routes/calendarRoutes.ts @@ -6,6 +6,10 @@ import logger from "../logger/logger"; import Session from "../models/session"; import * as calendarController from "../controllers/calendarController"; +import { + PASSPORT_FAILURE_REDIRECT_URL, + PASSPORT_SUCCESS_REDIRECT_URL, +} from "../utils/constants"; router.get( "/auth/google", @@ -24,7 +28,7 @@ router.get( router.get( "/auth/google/callback", passport.authenticate("google", { - failureRedirect: process.env.PASSPORT_FAILURE_REDIRECT_URL as string, + failureRedirect: PASSPORT_FAILURE_REDIRECT_URL, }), function (req, res) { // Successful authentication, redirect home. @@ -38,7 +42,7 @@ router.get( if (!sessionId) { logger.error("session cookie not found"); - res.redirect(process.env.PASSPORT_FAILURE_REDIRECT_URL as string); + res.redirect(PASSPORT_FAILURE_REDIRECT_URL); return; } @@ -50,7 +54,7 @@ router.get( .then((userSession) => { if (!userSession) { logger.error("user session not found"); - res.redirect(process.env.PASSPORT_FAILURE_REDIRECT_URL as string); + res.redirect(PASSPORT_FAILURE_REDIRECT_URL); return; } @@ -70,11 +74,11 @@ router.get( logger.info("err %s", err); }); - res.redirect(process.env.PASSPORT_SUCCESS_REDIRECT_URL as string); + res.redirect(PASSPORT_SUCCESS_REDIRECT_URL); }) .catch((err) => { logger.error(err); - res.redirect(process.env.PASSPORT_FAILURE_REDIRECT_URL as string); + res.redirect(PASSPORT_FAILURE_REDIRECT_URL); }); // /api/v1/addGoogleAccount diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index 74dbb59..22b2621 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -8,7 +8,7 @@ router.post("/auth/signup", userController.SignUp); router.post("/auth/login", userController.Login); router.delete("/auth/logout", sessionProtection, userController.Logout); router.get("/", sessionProtection, userController.GetUser); -router.post("/auth/check/accountname", userController.IsAccountNameAvailable); +router.post("/auth/check/email", userController.IsEmailAvailable); router.get( "/profile/settings", sessionProtection, @@ -45,5 +45,6 @@ router.post( userController.ExportUserAccount ); router.get("/profile/export/:id", userController.GetExportedUserAccount); +router.get("/verify/:state/:emailVerificationId", userController.VerifyEmail); export default router; diff --git a/src/routes/websiteRoutes.ts b/src/routes/websiteRoutes.ts index 4bf432f..0c76831 100644 --- a/src/routes/websiteRoutes.ts +++ b/src/routes/websiteRoutes.ts @@ -1,4 +1,4 @@ -import express from "express"; +/*import express from "express"; const router = express.Router(); import * as websiteController from "../controllers/websiteController"; @@ -11,3 +11,4 @@ router.post("/", websiteController.CreateWebsite); router.get("/:storeId", websiteController.GetWebsite); export default router; +*/ diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 7f4a72d..e7ba705 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -5,8 +5,9 @@ export const USER_SESSION_LENGTH = 32; export const USERNAME_MIN_LENGTH = 3; export const USERNAME_MAX_LENGTH = 20; -export const ACCOUNT_NAME_MIN_LENGTH = 3; -export const ACCOUNT_NAME_MAX_LENGTH = 20; +export const EMAIL_MIN_LENGTH = 3; +export const EMAIL_MAX_LENGTH = 64; +export const EMAIL_REGEX = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; export const PASSWORD_MIN_LENGTH = 8; export const PASSWORD_MAX_LENGTH = 64; @@ -51,6 +52,7 @@ export enum ACCOUNT_STATE { PENDING_DELETION = 1, // account still exists but is marked as deleted, can be restored or will be deleted after a certain time INIT_LOGIN = 2, // account is created but password is not set yet BANNED = 3, // account is banned, cannot login + PENDING_EMAIL_VERIFICATION = 4, // account is created but email is not verified yet } export const FEEDBACK_MIN_LENGTH = 3; @@ -69,3 +71,13 @@ export const Roles = { export const ACCOUNT_DEMO_DAYS = Number( process.env.CONSTANTS_ACCOUNT_DEMO_DAYS ); // how many days a demo account is valid until payment is required or account will be deleted + +export enum EMAIL_VERIFICATION_STATE { + PENDING_EMAIL_VERIFICATION = 0, // account is created but email is not verified yet +} + +export const DASHBOARD_URL = process.env.DASHBOARD_URL as string; +export const GOOGLE_CALLBACK_URL = `${process.env.GOOGLE_CALLBACK_URL}/v1/calendar/auth/google/callback`; +export const PASSPORT_FAILURE_REDIRECT_URL = `${process.env.DASHBOARD_URL}/store/calendar/auth/failed`; +export const PASSPORT_SUCCESS_REDIRECT_URL = `${process.env.DASHBOARD_URL}/store/calendar/auth/finish`; +export const ACCOUNT_EXPORT_URL = `${process.env.DASHBOARD_API_URL}/v1/user/profile/export/`; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 0a56028..190418a 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -4,6 +4,7 @@ import { v4 as uuidv4 } from "uuid"; import { Request, Response } from "express"; import Session from "../models/session"; import { + DASHBOARD_URL, DEFAULT_SESSION_EXPIRY, HEADER_X_AUTHORIZATION, SESSION_EXPIRY_NOT_REMEMBER_ME, @@ -42,6 +43,10 @@ export function newUserSession() { return crypto.randomBytes(USER_SESSION_LENGTH).toString("hex"); } +export function newEmailVerificationId() { + return crypto.randomBytes(64).toString("hex"); +} + export function newSessionId() { return uuidv4(); } @@ -57,6 +62,7 @@ export function newAccountExportId() { export async function saveSession( req: Request, res: Response, + sendResponseData: boolean, userId: string, username: string, rememberMe: boolean @@ -77,12 +83,15 @@ export async function saveSession( ), }); - res.status(200).json({ - XAuthorization: userSession, - Username: username, - }); + if (sendResponseData) { + res.status(200).json({ + XAuthorization: userSession, + Username: username, + }); + } else { + res.status(200).send({ msg: "success" }); + } } catch (err) { - console.log(err); res.status(500).send({ err: "invalid request" }); } } @@ -100,3 +109,10 @@ export async function getUserSession(req: Request) { }, }); } + +export function getEmailVerificationUrl( + state: string, + emailVerificationId: string +) { + return `${DASHBOARD_URL}/verify/${state}/${emailVerificationId}`; +} diff --git a/src/validator/validator.ts b/src/validator/validator.ts index b81d91e..21286f3 100644 --- a/src/validator/validator.ts +++ b/src/validator/validator.ts @@ -3,8 +3,6 @@ import { USERNAME_MAX_LENGTH, PASSWORD_MIN_LENGTH, PASSWORD_MAX_LENGTH, - ACCOUNT_NAME_MIN_LENGTH, - ACCOUNT_NAME_MAX_LENGTH, USER_ID_LENGTH, STORE_SERVICE_MIN_LENGTH, STORE_SERVICE_MAX_LENGTH, @@ -20,6 +18,9 @@ import { FEEDBACK_MIN_LENGTH, COMPANY_NAME_MIN_LENGTH, COMPANY_NAME_MAX_LENGTH, + EMAIL_MIN_LENGTH, + EMAIL_MAX_LENGTH, + EMAIL_REGEX, } from "../utils/constants"; import User from "../models/user"; @@ -31,20 +32,21 @@ export function isUsernameValid(username: string) { ); } -// TODO: regex for account name -export async function isAccountNameValid(accountName: string) { +// TODO: regex for email +export async function isEmailValid(email: string) { if ( - accountName.length < ACCOUNT_NAME_MIN_LENGTH || - accountName.length > ACCOUNT_NAME_MAX_LENGTH + email.length < EMAIL_MIN_LENGTH || + email.length > EMAIL_MAX_LENGTH || + !EMAIL_REGEX.test(email) ) { return false; } - // check if account name is already taken in the database + // check if email is already taken in the database const existingUser = await User.findOne({ where: { - account_name: accountName, + email: email, }, }); @@ -63,11 +65,6 @@ export function isPasswordValid(password: string) { ); } -// TODO: regex for email -export function isEmailValid(email: string) { - return true; -} - export function isUserIdValid(userId: string) { return userId.length === USER_ID_LENGTH; }