changed to email

main
alex 2024-02-10 13:52:13 +01:00
parent 7a78649010
commit bd1fedb397
15 changed files with 309 additions and 165 deletions

View File

@ -7,27 +7,22 @@ MARIADB_DATABASE=terminplanner
MARIADB_USER=terminplanner MARIADB_USER=terminplanner
MARIADB_PASSWORD=your_password MARIADB_PASSWORD=your_password
DASHBOARD_URL=
DASHBOARD_API_URL=
GOOGLE_CLIENT_ID=your_client_id GOOGLE_CLIENT_ID=your_client_id
GOOGLE_CLIENT_SECRET=your_client_secret GOOGLE_CLIENT_SECRET=your_client_secret
GOOGLE_CALLBACK_URL=your_callback_url 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_AUTHORIZATION_PASSWORD=your_authorization_password
TERMIN_PLANNER_URL=your_termin_planner_url 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_HOST=
RABBITMQ_PORT= RABBITMQ_PORT=
RABBITMQ_USERNAME= RABBITMQ_USERNAME=
RABBITMQ_PASSWORD= RABBITMQ_PASSWORD=
RABBITMQ_MAIL_QUEUE= RABBITMQ_MAIL_QUEUE=
ACCOUNT_EXPORT_URL= RECAPTCHA_SECRET_KEY=
RECAPTCHA_SECRET_KEY= CONSTANTS_ACCOUNT_DEMO_DAYS=

View File

@ -7,14 +7,13 @@ import GoogleStrategy from "passport-google-oauth20";
import cookieParser from "cookie-parser"; import cookieParser from "cookie-parser";
import session from "express-session"; import session from "express-session";
import useragent from "express-useragent"; import useragent from "express-useragent";
import rabbitmq from "./src/rabbitmq/rabbitmq";
import calendarRoutes from "./src/routes/calendarRoutes"; import calendarRoutes from "./src/routes/calendarRoutes";
import storeRoutes from "./src/routes/storeRoutes"; import storeRoutes from "./src/routes/storeRoutes";
import storeServicesRoutes from "./src/routes/storeServicesRoutes"; import storeServicesRoutes from "./src/routes/storeServicesRoutes";
import userRoutes from "./src/routes/userRoutes"; import userRoutes from "./src/routes/userRoutes";
import usersRoutes from "./src/routes/usersRoutes"; import usersRoutes from "./src/routes/usersRoutes";
import websiteRoutes from "./src/routes/websiteRoutes"; //import websiteRoutes from "./src/routes/websiteRoutes";
dotenv.config(); dotenv.config();
@ -22,8 +21,9 @@ import swaggerJsDoc from "swagger-jsdoc";
import syncModels from "./src/models/index"; import syncModels from "./src/models/index";
import logger from "./src/logger/logger"; import logger from "./src/logger/logger";
import passport from "passport"; import passport from "passport";
import fs from "fs-extra"; import rabbitmq from "./src/rabbitmq/rabbitmq";
import { cloneWebsiteTemplate } from "./src/controllers/websiteController"; import { GOOGLE_CALLBACK_URL } from "./src/utils/constants";
//import { cloneWebsiteTemplate } from "./src/controllers/websiteController";
const app: Express = express(); const app: Express = express();
const host = process.env.HOST || "localhost"; const host = process.env.HOST || "localhost";
const port = Number(process.env.PORT) || 3000; const port = Number(process.env.PORT) || 3000;
@ -68,7 +68,7 @@ passport.use(
{ {
clientID: process.env.GOOGLE_CLIENT_ID as string, clientID: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
callbackURL: process.env.GOOGLE_CALLBACK_URL as string, callbackURL: GOOGLE_CALLBACK_URL,
/*scope: [ /*scope: [
"https://www.googleapis.com/auth/calendar.app.created", "https://www.googleapis.com/auth/calendar.app.created",
"https://www.googleapis.com/auth/calendar.events.freebusy", "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/store/services", storeServicesRoutes);
app.use("/api/v1/user", userRoutes); app.use("/api/v1/user", userRoutes);
app.use("/api/v1/users", usersRoutes); app.use("/api/v1/users", usersRoutes);
app.use("/api/v1/website", websiteRoutes); //app.use("/api/v1/website", websiteRoutes);
const specs = swaggerJsDoc(options); const specs = swaggerJsDoc(options);
app.use("/api-docs", swaggerUI.serve, swaggerUI.setup(specs)); app.use("/api-docs", swaggerUI.serve, swaggerUI.setup(specs));
@ -135,7 +135,7 @@ app.use((err: any, req: any, res: any, next: any) => {
syncModels(); syncModels();
// create tmp folder if not exists // create tmp folder if not exists
/*
[ [
process.env.WEBSITE_BUILDER_TMP_DIR as string, process.env.WEBSITE_BUILDER_TMP_DIR as string,
process.env.WEBSITE_BUILDER_TMP_CUSTOMER_WEBSITES_DIR as string, process.env.WEBSITE_BUILDER_TMP_CUSTOMER_WEBSITES_DIR as string,
@ -154,16 +154,21 @@ if (
) { ) {
cloneWebsiteTemplate(); cloneWebsiteTemplate();
} }
*/
// start server // start server
rabbitmq( rabbitmq
process.env.RABBITMQ_USERNAME as string, .connect(
process.env.RABBITMQ_PASSWORD as string, process.env.RABBITMQ_USERNAME as string,
process.env.RABBITMQ_HOST as string, process.env.RABBITMQ_PASSWORD as string,
process.env.RABBITMQ_PORT as string process.env.RABBITMQ_HOST as string,
); process.env.RABBITMQ_PORT as string
)
app.listen(port, host, () => .then(() => {
logger.info(`⚡️[server]: Server is running at http://${host}:${port}`) app.listen(port, host, () =>
); logger.info(`⚡️[server]: Server is running at http://${host}:${port}`)
);
})
.catch((err) => {
logger.error(err);
});

View File

@ -2,7 +2,6 @@ import { Request, Response } from "express";
import logger from "../logger/logger"; import logger from "../logger/logger";
import User from "../models/user"; import User from "../models/user";
import { import {
isAccountNameValid,
isCompanyNameValid, isCompanyNameValid,
isEmailValid, isEmailValid,
isLanguageCodeValid, isLanguageCodeValid,
@ -15,15 +14,19 @@ import {
CALENDAR_MAX_FUTURE_BOOKING_DAYS, CALENDAR_MAX_FUTURE_BOOKING_DAYS,
CALENDAR_MAX_SERVICE_DURATION, CALENDAR_MAX_SERVICE_DURATION,
CALENDAR_MIN_EARLIEST_BOOKING_TIME, CALENDAR_MIN_EARLIEST_BOOKING_TIME,
DASHBOARD_URL,
EMAIL_VERIFICATION_STATE,
Roles, Roles,
USER_ANALYTICS_ENABLED_DEFAULT, USER_ANALYTICS_ENABLED_DEFAULT,
} from "../utils/constants"; } from "../utils/constants";
import { import {
decodeBase64, decodeBase64,
getEmailVerificationUrl,
getUserSession, getUserSession,
hashPassword, hashPassword,
matchPassword, matchPassword,
newAccountExportId, newAccountExportId,
newEmailVerificationId,
newFeedbackId, newFeedbackId,
newStoreId, newStoreId,
newUserId, newUserId,
@ -33,15 +36,16 @@ import Store from "../models/store";
import Session from "../models/session"; import Session from "../models/session";
import Feedback from "../models/feedback"; import Feedback from "../models/feedback";
import fs from "fs-extra"; import fs from "fs-extra";
import { channel } from "../rabbitmq/rabbitmq"; import rabbitmq from "../rabbitmq/rabbitmq";
import verifyCaptcha from "../utils/recaptcha"; import verifyCaptcha from "../utils/recaptcha";
import EmailVerification from "../models/emailVerification";
export async function SignUp(req: Request, res: Response) { export async function SignUp(req: Request, res: Response) {
try { try {
let { let {
companyName, companyName,
username, username,
accountName, email,
password, password,
language, language,
rememberMe, rememberMe,
@ -53,12 +57,12 @@ export async function SignUp(req: Request, res: Response) {
if ( if (
!companyName || !companyName ||
!username || !username ||
!accountName || !email ||
!password || !password ||
!language || !language ||
!isCompanyNameValid(companyName) || !isCompanyNameValid(companyName) ||
!isUsernameValid(username) || !isUsernameValid(username) ||
!isAccountNameValid(accountName) || !isEmailValid(email) ||
!isLanguageCodeValid(language) || !isLanguageCodeValid(language) ||
rememberMe === undefined || rememberMe === undefined ||
!recaptcha !recaptcha
@ -77,13 +81,13 @@ export async function SignUp(req: Request, res: Response) {
return res.status(400).send({ err: "invalid request" }); return res.status(400).send({ err: "invalid request" });
} }
accountName = accountName.toLowerCase(); email = email.toLowerCase();
// check if user already exists // check if user already exists
const existingUser = await User.findOne({ const existingUser = await User.findOne({
where: { where: {
account_name: accountName, email: email,
}, },
}); });
@ -107,7 +111,7 @@ export async function SignUp(req: Request, res: Response) {
let userId = newUserId(); let userId = newUserId();
Store.create({ const store = await Store.create({
store_id: newStoreId(), store_id: newStoreId(),
owner_user_id: userId, owner_user_id: userId,
name: companyName, 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_min_earliest_booking_time: CALENDAR_MIN_EARLIEST_BOOKING_TIME,
calendar_max_service_duration: CALENDAR_MAX_SERVICE_DURATION, calendar_max_service_duration: CALENDAR_MAX_SERVICE_DURATION,
address: "", address: "",
}) });
.then(async (store) => {
// create user
await User.create({ // create email verification
user_id: userId,
store_id: store.store_id, const emailVerificationId = newEmailVerificationId();
role: Roles.Master, const state = EMAIL_VERIFICATION_STATE.PENDING_EMAIL_VERIFICATION;
account_name: accountName,
username: username, await EmailVerification.create({
password: hashedPassword, email_verification_id: emailVerificationId,
language: language, user_id: userId,
analytics_enabled: USER_ANALYTICS_ENABLED_DEFAULT, state: state,
state: ACCOUNT_STATE.ACTIVE, });
})
.then((user) => { rabbitmq.sendEmail(email, "dashboardSignUpEmailVerification", language, {
// create session emailVerificationUrl: getEmailVerificationUrl(
saveSession(req, res, user.user_id, user.username, rememberMe); String(state),
}) emailVerificationId
.catch((err) => { ),
logger.error(err); });
res.status(500).send({ err: "invalid request" });
}); // create user
})
.catch((err) => { const user = await User.create({
logger.error(err); user_id: userId,
res.status(500).send({ err: "invalid request" }); 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) { } catch (error) {
logger.error(error); logger.error(error);
res.status(500).send({ err: "invalid request" }); 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) { export async function Login(req: Request, res: Response) {
try { try {
let { accountName, password, rememberMe, recaptcha } = req.body; let { email, password, rememberMe, recaptcha } = req.body;
// validate request // validate request
if (!accountName) { if (!email) {
return res.status(400).send({ err: "invalid request" }); 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" }); return res.status(400).send({ err: "invalid request" });
} }
// check if user exists // check if user exists
const user = await User.findOne({ const user = await User.findOne({
where: { where: {
account_name: accountName, email: email,
}, },
attributes: ["user_id", "password", "state"], 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 // if password not provided, then send user state
// user is on the login page on the first step of the login process // 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) { if (password === undefined) {
return res.status(200).send({ state: user.state }); return res.status(200).send({ state: user.state });
@ -244,7 +255,7 @@ export async function Login(req: Request, res: Response) {
} }
// create session // create session
saveSession(req, res, user.user_id, user.username, rememberMe); saveSession(req, res, true, user.user_id, user.username, rememberMe);
} catch (error) { } catch (error) {
logger.error(error); logger.error(error);
res.status(500).send({ err: "invalid request" }); 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 { try {
let { accountName } = req.body; let { email } = req.body;
// validate request // validate request
if (!accountName) { if (!email) {
return res.status(400).send({ err: "invalid request" }); 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" }); 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({ const user = await User.findOne({
where: { 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" }); 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) { } catch (error) {
logger.error(error); logger.error(error);
res.status(500).send({ err: "invalid request" }); res.status(500).send({ err: "invalid request" });
@ -432,7 +443,7 @@ export async function GetUserProfileSettings(req: Request, res: Response) {
where: { where: {
user_id: session.user_id, user_id: session.user_id,
}, },
attributes: ["language", "analytics_enabled", "username", "account_name"], attributes: ["language", "analytics_enabled", "username", "email"],
}); });
res.status(200).json(user); 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) { export async function UpdateUserProfileSettings(req: Request, res: Response) {
try { try {
const { language, analyticsEnabled, username, accountName } = req.body; const { language, analyticsEnabled, username, email } = req.body;
if ( if (!language && analyticsEnabled === undefined && !username && !email) {
!language &&
analyticsEnabled === undefined &&
!username &&
!accountName
) {
return res.status(400).send({ err: "invalid request" }); return res.status(400).send({ err: "invalid request" });
} }
@ -487,12 +493,12 @@ export async function UpdateUserProfileSettings(req: Request, res: Response) {
user.username = username; user.username = username;
} }
if (accountName) { if (email) {
if (!isAccountNameValid(accountName)) { if (!isEmailValid(email)) {
return res.status(400).send({ err: "invalid request" }); return res.status(400).send({ err: "invalid request" });
} }
user.account_name = accountName; user.email = email;
} }
await user.save(); await user.save();
@ -757,7 +763,7 @@ export async function ExportUserAccount(req: Request, res: Response) {
user: { user: {
user_id: user.user_id, user_id: user.user_id,
username: user.username, username: user.username,
account_name: user.account_name, email: user.email,
calendar_max_future_booking_days: calendar_max_future_booking_days:
user.calendar_max_future_booking_days, user.calendar_max_future_booking_days,
calendar_min_earliest_booking_time: calendar_min_earliest_booking_time:
@ -778,22 +784,11 @@ export async function ExportUserAccount(req: Request, res: Response) {
// send email with file // send email with file
channel.sendToQueue( rabbitmq.sendEmail(email, "dashboardUserAccountExportFinish", "de", {
process.env.RABBITMQ_MAIL_QUEUE as string, accountExportDownloadUrl: `${
Buffer.from( process.env.ACCOUNT_EXPORT_URL as string
JSON.stringify({ }${accountExportId}`,
m: email, // UserMail });
t: "dashboardUserAccountExportFinish", // TemplateId
l: "de", // LanguageId
// BodyData
b: {
accountExportDownloadUrl: `${
process.env.ACCOUNT_EXPORT_URL as string
}${accountExportId}`,
},
})
)
);
} catch (error) { } catch (error) {
logger.error(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 { try {
const { id } = req.params; const { id } = req.params;
@ -830,3 +825,53 @@ export function GetExportedUserAccount(req: Request, res: Response) {
res.status(500).send({ err: "invalid request" }); 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" });
}
}

View File

@ -1,7 +1,7 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import logger from "../logger/logger"; import logger from "../logger/logger";
import { import {
isAccountNameValid, isEmailValid,
isLanguageCodeValid, isLanguageCodeValid,
isPasswordValid, isPasswordValid,
isUserIdValid, isUserIdValid,
@ -27,7 +27,7 @@ export async function AddEmployee(req: Request, res: Response) {
let { let {
storeId, storeId,
username, username,
accountName, email,
password, password,
calendarMaxFutureBookingDays, calendarMaxFutureBookingDays,
calendarMinEarliestBookingTime, calendarMinEarliestBookingTime,
@ -40,18 +40,15 @@ export async function AddEmployee(req: Request, res: Response) {
if ( if (
!storeId || !storeId ||
!username || !username ||
!accountName || !email ||
passwordSetOnInitLogging === undefined || passwordSetOnInitLogging === undefined ||
(!password && passwordSetOnInitLogging === false) || (!password && passwordSetOnInitLogging === false) ||
!language || !language ||
!isLanguageCodeValid(language) !isLanguageCodeValid(language)
) { ) {
logger.debug("Invalid request");
return res.status(400).send({ err: "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 // verify if requester is a store master
const requesterSession = await getUserSession(req); const requesterSession = await getUserSession(req);
@ -74,14 +71,11 @@ export async function AddEmployee(req: Request, res: Response) {
return res.status(401).send({ err: "unauthorized" }); return res.status(401).send({ err: "unauthorized" });
} }
// validate username and account name // validate username and email
accountName = accountName.toLowerCase(); email = email.toLowerCase();
if ( if (!isUsernameValid(username) || !(await isEmailValid(email))) {
!isUsernameValid(username) ||
!(await isAccountNameValid(accountName))
) {
return res.status(400).send({ err: "invalid request" }); 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({ const existingUser = await User.findOne({
where: { where: {
account_name: accountName, email: email,
}, },
}); });
@ -105,7 +99,7 @@ export async function AddEmployee(req: Request, res: Response) {
user_id: userId, user_id: userId,
store_id: storeId, store_id: storeId,
role: Roles.Worker, role: Roles.Worker,
account_name: accountName, email: email,
username: username, username: username,
calendar_max_future_booking_days: calendarMaxFutureBookingDays, calendar_max_future_booking_days: calendarMaxFutureBookingDays,
calendar_min_earliest_booking_time: calendarMinEarliestBookingTime, calendar_min_earliest_booking_time: calendarMinEarliestBookingTime,
@ -136,7 +130,7 @@ export async function AddEmployee(req: Request, res: Response) {
user_id: string; user_id: string;
store_id: any; store_id: any;
role: string; role: string;
account_name: any; email: any;
username: any; username: any;
calendar_max_future_booking_days: any; calendar_max_future_booking_days: any;
calendar_min_earliest_booking_time: 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" }); 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({ const employees = await User.findAll({
where: { where: {
@ -202,7 +196,7 @@ export async function GetEmployees(req: Request, res: Response) {
attributes: [ attributes: [
"user_id", "user_id",
"username", "username",
"account_name", "email",
"calendar_max_future_booking_days", "calendar_max_future_booking_days",
"calendar_min_earliest_booking_time", "calendar_min_earliest_booking_time",
], ],
@ -234,13 +228,11 @@ export async function UpdateEmployee(req: Request, res: Response) {
let { let {
userId, userId,
username, username,
accountName, email,
calendarMaxFutureBookingDays, calendarMaxFutureBookingDays,
calendarMinEarliestBookingTime, calendarMinEarliestBookingTime,
} = req.body; } = req.body;
// validate username and account name
if (!isUserIdValid(userId)) { if (!isUserIdValid(userId)) {
return res.status(400).send({ err: "invalid request" }); return res.status(400).send({ err: "invalid request" });
} }
@ -282,17 +274,17 @@ export async function UpdateEmployee(req: Request, res: Response) {
let update = {}; let update = {};
if (accountName) { if (email) {
accountName = accountName.toLowerCase(); email = email.toLowerCase();
if (!(await isAccountNameValid(accountName))) { if (!(await isEmailValid(email))) {
res.status(400).send({ err: "invalid request" }); res.status(400).send({ err: "invalid request" });
return; return;
} }
update = { update = {
...update, ...update,
account_name: accountName, email: email,
}; };
} }

View File

@ -1,4 +1,4 @@
import { Request, Response } from "express"; /*import { Request, Response } from "express";
import logger from "../logger/logger"; import logger from "../logger/logger";
import util from "util"; import util from "util";
import { exec } from "child_process"; import { exec } from "child_process";
@ -159,3 +159,4 @@ export async function GetWebsite(req: Request, res: Response) {
res.status(500).send({ error: "invalid request" }); res.status(500).send({ error: "invalid request" });
} }
} }
*/

View File

@ -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<EmailVerificationAttributes>
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;

View File

@ -1,4 +1,5 @@
import Feedback from "./feedback"; import Feedback from "./feedback";
import EmailVerification from "./emailVerification";
import Session from "./session"; import Session from "./session";
import Store from "./store"; import Store from "./store";
import StoreService from "./storeService"; import StoreService from "./storeService";
@ -8,6 +9,7 @@ import User from "./user";
import Website from "./website"; import Website from "./website";
function syncModels() { function syncModels() {
EmailVerification.sync();
User.sync(); User.sync();
Session.sync(); Session.sync();
Store.sync(); Store.sync();

View File

@ -6,7 +6,7 @@ interface UserAttributes {
store_id: string; store_id: string;
// TODO: change to role_id // TODO: change to role_id
role: string; role: string;
account_name: string; email: string;
username: string; username: string;
password?: string; // can be null if user is created by store owner - password is set by user when they first login 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; calendar_max_future_booking_days?: number;
@ -21,7 +21,7 @@ class User extends Model<UserAttributes> implements UserAttributes {
declare user_id: string; declare user_id: string;
declare store_id: string; declare store_id: string;
declare role: string; declare role: string;
declare account_name: string; declare email: string;
declare username: string; declare username: string;
declare password: string; declare password: string;
declare calendar_max_future_booking_days: number; declare calendar_max_future_booking_days: number;
@ -49,7 +49,7 @@ User.init(
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false, allowNull: false,
}, },
account_name: { email: {
type: DataTypes.STRING, type: DataTypes.STRING,
unique: true, unique: true,
}, },

View File

@ -1,6 +1,6 @@
import amqplib from "amqplib"; import amqplib from "amqplib";
export let channel: amqplib.Channel; let channel: amqplib.Channel;
async function connect( async function connect(
RABBITMQ_USERNAME: string, RABBITMQ_USERNAME: string,
@ -13,16 +13,45 @@ async function connect(
); );
if (!open) { if (!open) {
console.log("RabbitMQ connection failed"); console.log("⚡️[RabbitMQ]: connection failed");
return; return;
} }
channel = await open.createChannel(); channel = await open.createChannel();
if (!channel) { if (!channel) {
console.log("RabbitMQ channel failed"); console.log("⚡️[RabbitMQ]: channel failed");
return; 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;

View File

@ -6,6 +6,10 @@ import logger from "../logger/logger";
import Session from "../models/session"; import Session from "../models/session";
import * as calendarController from "../controllers/calendarController"; import * as calendarController from "../controllers/calendarController";
import {
PASSPORT_FAILURE_REDIRECT_URL,
PASSPORT_SUCCESS_REDIRECT_URL,
} from "../utils/constants";
router.get( router.get(
"/auth/google", "/auth/google",
@ -24,7 +28,7 @@ router.get(
router.get( router.get(
"/auth/google/callback", "/auth/google/callback",
passport.authenticate("google", { passport.authenticate("google", {
failureRedirect: process.env.PASSPORT_FAILURE_REDIRECT_URL as string, failureRedirect: PASSPORT_FAILURE_REDIRECT_URL,
}), }),
function (req, res) { function (req, res) {
// Successful authentication, redirect home. // Successful authentication, redirect home.
@ -38,7 +42,7 @@ router.get(
if (!sessionId) { if (!sessionId) {
logger.error("session cookie not found"); logger.error("session cookie not found");
res.redirect(process.env.PASSPORT_FAILURE_REDIRECT_URL as string); res.redirect(PASSPORT_FAILURE_REDIRECT_URL);
return; return;
} }
@ -50,7 +54,7 @@ router.get(
.then((userSession) => { .then((userSession) => {
if (!userSession) { if (!userSession) {
logger.error("user session not found"); logger.error("user session not found");
res.redirect(process.env.PASSPORT_FAILURE_REDIRECT_URL as string); res.redirect(PASSPORT_FAILURE_REDIRECT_URL);
return; return;
} }
@ -70,11 +74,11 @@ router.get(
logger.info("err %s", err); logger.info("err %s", err);
}); });
res.redirect(process.env.PASSPORT_SUCCESS_REDIRECT_URL as string); res.redirect(PASSPORT_SUCCESS_REDIRECT_URL);
}) })
.catch((err) => { .catch((err) => {
logger.error(err); logger.error(err);
res.redirect(process.env.PASSPORT_FAILURE_REDIRECT_URL as string); res.redirect(PASSPORT_FAILURE_REDIRECT_URL);
}); });
// /api/v1/addGoogleAccount // /api/v1/addGoogleAccount

View File

@ -8,7 +8,7 @@ router.post("/auth/signup", userController.SignUp);
router.post("/auth/login", userController.Login); router.post("/auth/login", userController.Login);
router.delete("/auth/logout", sessionProtection, userController.Logout); router.delete("/auth/logout", sessionProtection, userController.Logout);
router.get("/", sessionProtection, userController.GetUser); router.get("/", sessionProtection, userController.GetUser);
router.post("/auth/check/accountname", userController.IsAccountNameAvailable); router.post("/auth/check/email", userController.IsEmailAvailable);
router.get( router.get(
"/profile/settings", "/profile/settings",
sessionProtection, sessionProtection,
@ -45,5 +45,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);
export default router; export default router;

View File

@ -1,4 +1,4 @@
import express from "express"; /*import express from "express";
const router = express.Router(); const router = express.Router();
import * as websiteController from "../controllers/websiteController"; import * as websiteController from "../controllers/websiteController";
@ -11,3 +11,4 @@ router.post("/", websiteController.CreateWebsite);
router.get("/:storeId", websiteController.GetWebsite); router.get("/:storeId", websiteController.GetWebsite);
export default router; export default router;
*/

View File

@ -5,8 +5,9 @@ export const USER_SESSION_LENGTH = 32;
export const USERNAME_MIN_LENGTH = 3; export const USERNAME_MIN_LENGTH = 3;
export const USERNAME_MAX_LENGTH = 20; export const USERNAME_MAX_LENGTH = 20;
export const ACCOUNT_NAME_MIN_LENGTH = 3; export const EMAIL_MIN_LENGTH = 3;
export const ACCOUNT_NAME_MAX_LENGTH = 20; 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_MIN_LENGTH = 8;
export const PASSWORD_MAX_LENGTH = 64; 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 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 INIT_LOGIN = 2, // account is created but password is not set yet
BANNED = 3, // account is banned, cannot login 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; export const FEEDBACK_MIN_LENGTH = 3;
@ -69,3 +71,13 @@ export const Roles = {
export const ACCOUNT_DEMO_DAYS = Number( export const ACCOUNT_DEMO_DAYS = Number(
process.env.CONSTANTS_ACCOUNT_DEMO_DAYS process.env.CONSTANTS_ACCOUNT_DEMO_DAYS
); // how many days a demo account is valid until payment is required or account will be deleted ); // 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/`;

View File

@ -4,6 +4,7 @@ import { v4 as uuidv4 } from "uuid";
import { Request, Response } from "express"; import { Request, Response } from "express";
import Session from "../models/session"; import Session from "../models/session";
import { import {
DASHBOARD_URL,
DEFAULT_SESSION_EXPIRY, DEFAULT_SESSION_EXPIRY,
HEADER_X_AUTHORIZATION, HEADER_X_AUTHORIZATION,
SESSION_EXPIRY_NOT_REMEMBER_ME, SESSION_EXPIRY_NOT_REMEMBER_ME,
@ -42,6 +43,10 @@ export function newUserSession() {
return crypto.randomBytes(USER_SESSION_LENGTH).toString("hex"); return crypto.randomBytes(USER_SESSION_LENGTH).toString("hex");
} }
export function newEmailVerificationId() {
return crypto.randomBytes(64).toString("hex");
}
export function newSessionId() { export function newSessionId() {
return uuidv4(); return uuidv4();
} }
@ -57,6 +62,7 @@ export function newAccountExportId() {
export async function saveSession( export async function saveSession(
req: Request, req: Request,
res: Response, res: Response,
sendResponseData: boolean,
userId: string, userId: string,
username: string, username: string,
rememberMe: boolean rememberMe: boolean
@ -77,12 +83,15 @@ export async function saveSession(
), ),
}); });
res.status(200).json({ if (sendResponseData) {
XAuthorization: userSession, res.status(200).json({
Username: username, XAuthorization: userSession,
}); Username: username,
});
} else {
res.status(200).send({ msg: "success" });
}
} catch (err) { } catch (err) {
console.log(err);
res.status(500).send({ err: "invalid request" }); 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}`;
}

View File

@ -3,8 +3,6 @@ import {
USERNAME_MAX_LENGTH, USERNAME_MAX_LENGTH,
PASSWORD_MIN_LENGTH, PASSWORD_MIN_LENGTH,
PASSWORD_MAX_LENGTH, PASSWORD_MAX_LENGTH,
ACCOUNT_NAME_MIN_LENGTH,
ACCOUNT_NAME_MAX_LENGTH,
USER_ID_LENGTH, USER_ID_LENGTH,
STORE_SERVICE_MIN_LENGTH, STORE_SERVICE_MIN_LENGTH,
STORE_SERVICE_MAX_LENGTH, STORE_SERVICE_MAX_LENGTH,
@ -20,6 +18,9 @@ import {
FEEDBACK_MIN_LENGTH, FEEDBACK_MIN_LENGTH,
COMPANY_NAME_MIN_LENGTH, COMPANY_NAME_MIN_LENGTH,
COMPANY_NAME_MAX_LENGTH, COMPANY_NAME_MAX_LENGTH,
EMAIL_MIN_LENGTH,
EMAIL_MAX_LENGTH,
EMAIL_REGEX,
} from "../utils/constants"; } from "../utils/constants";
import User from "../models/user"; import User from "../models/user";
@ -31,20 +32,21 @@ export function isUsernameValid(username: string) {
); );
} }
// TODO: regex for account name // TODO: regex for email
export async function isAccountNameValid(accountName: string) { export async function isEmailValid(email: string) {
if ( if (
accountName.length < ACCOUNT_NAME_MIN_LENGTH || email.length < EMAIL_MIN_LENGTH ||
accountName.length > ACCOUNT_NAME_MAX_LENGTH email.length > EMAIL_MAX_LENGTH ||
!EMAIL_REGEX.test(email)
) { ) {
return false; 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({ const existingUser = await User.findOne({
where: { 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) { export function isUserIdValid(userId: string) {
return userId.length === USER_ID_LENGTH; return userId.length === USER_ID_LENGTH;
} }