From 3128b50540ca73451f89dac7c56f1b3220db30b6 Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 3 Feb 2024 22:36:30 +0100 Subject: [PATCH] export account --- env.example | 10 +- package-lock.json | 114 ++++++++++++++++ package.json | 4 +- server.ts | 9 ++ src/controllers/userController.ts | 123 ++++++++++++++++++ src/rabbitmq/rabbitmq.ts | 28 ++++ src/routes/userRoutes.ts | 6 + src/validator/validator.ts | 5 + .../4417984c-de81-4930-8ae7-9f178d98ddf7.json | 1 + 9 files changed, 298 insertions(+), 2 deletions(-) create mode 100644 src/rabbitmq/rabbitmq.ts create mode 100644 user-profile-exports/4417984c-de81-4930-8ae7-9f178d98ddf7.json diff --git a/env.example b/env.example index d021ae8..346b749 100644 --- a/env.example +++ b/env.example @@ -20,4 +20,12 @@ 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 \ No newline at end of file +WEBSITE_BUILDER_TMP_CUSTOMER_WEBSITES_DIR=./customer-websites + +RABBITMQ_HOST= +RABBITMQ_PORT= +RABBITMQ_USERNAME= +RABBITMQ_PASSWORD= +RABBITMQ_MAIL_QUEUE= + +ACCOUNT_EXPORT_URL= \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 21acc4a..ea96d4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "amqplib": "^0.10.3", "axios": "^1.6.5", "bcrypt": "^5.1.1", "body-parser": "^1.20.2", @@ -32,6 +33,7 @@ "uuid": "^9.0.1" }, "devDependencies": { + "@types/amqplib": "^0.10.4", "@types/bcrypt": "^5.0.2", "@types/cookie-parser": "^1.4.6", "@types/cors": "^2.8.17", @@ -46,6 +48,45 @@ "typescript": "^5.3.3" } }, + "node_modules/@acuminous/bitsyntax": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz", + "integrity": "sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==", + "dependencies": { + "buffer-more-ints": "~1.0.0", + "debug": "^4.3.4", + "safe-buffer": "~5.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/@acuminous/bitsyntax/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@acuminous/bitsyntax/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/@acuminous/bitsyntax/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", @@ -121,6 +162,15 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, + "node_modules/@types/amqplib": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@types/amqplib/-/amqplib-0.10.4.tgz", + "integrity": "sha512-Y5Sqquh/LqDxSgxYaAAFNM0M7GyONtSDCcFMJk+DQwYEjibPyW6y+Yu9H9omdkKc3epyXULmFN3GTaeBHhn2Hg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/bcrypt": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", @@ -431,6 +481,36 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/amqplib": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.3.tgz", + "integrity": "sha512-UHmuSa7n8vVW/a5HGh2nFPqAEr8+cD4dEZ6u9GjP91nHfr1a54RyAKyra7Sb5NH7NBKOUlyQSMXIp0qAixKexw==", + "dependencies": { + "@acuminous/bitsyntax": "^0.1.2", + "buffer-more-ints": "~1.0.0", + "readable-stream": "1.x >=1.1.9", + "url-parse": "~1.5.10" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/amqplib/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/amqplib/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -603,6 +683,11 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-more-ints": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -812,6 +897,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -1509,6 +1599,11 @@ "node": ">=8" } }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -2114,6 +2209,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", @@ -2188,6 +2288,11 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/retry-as-promised": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.4.tgz", @@ -2708,6 +2813,15 @@ "node": ">= 0.8" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index b8a2d3f..c4b7f97 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,12 @@ "scripts": { "build": "npx tsc", "start": "node build/server.js", - "dev": "concurrently \"npx tsc --watch\" \"nodemon --ignore ./tmp/ --ignore ./customer-websites/ -q build/server.js | pino-pretty\"" + "dev": "concurrently \"npx tsc --watch\" \"nodemon --ignore ./tmp/ --ignore ./customer-websites/ --ignore ./user-profile-exports/ -q build/server.js | pino-pretty\"" }, "author": "", "license": "ISC", "dependencies": { + "amqplib": "^0.10.3", "axios": "^1.6.5", "bcrypt": "^5.1.1", "body-parser": "^1.20.2", @@ -34,6 +35,7 @@ "uuid": "^9.0.1" }, "devDependencies": { + "@types/amqplib": "^0.10.4", "@types/bcrypt": "^5.0.2", "@types/cookie-parser": "^1.4.6", "@types/cors": "^2.8.17", diff --git a/server.ts b/server.ts index 153d6d8..64b2904 100644 --- a/server.ts +++ b/server.ts @@ -7,6 +7,7 @@ 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"; @@ -23,6 +24,7 @@ import logger from "./src/logger/logger"; import passport from "passport"; import fs from "fs-extra"; import { cloneWebsiteTemplate } from "./src/controllers/websiteController"; +import { connect } from "amqplib"; const app: Express = express(); const host = process.env.HOST || "localhost"; const port = Number(process.env.PORT) || 3000; @@ -156,6 +158,13 @@ if ( // 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}`) ); diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index 479e901..2b19280 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -4,6 +4,7 @@ import User from "../models/user"; import { isAccountNameValid, isCompanyNameValid, + isEmailValid, isLanguageCodeValid, isPasswordValid, isUsernameValid, @@ -29,6 +30,8 @@ import { 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"; export async function SignUp(req: Request, res: Response) { try { @@ -644,3 +647,123 @@ export async function DeleteUserProfile(req: Request, res: Response) { res.status(500).send({ err: "invalid request" }); } } + +export async function ExportUserAccount(req: Request, res: Response) { + try { + const { email, password } = req.body; + + if ( + !email || + !password || + !isEmailValid(email) || + !isPasswordValid(password) + ) { + return res.status(400).send({ err: "invalid request" }); + } + + 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, + }, + }); + + 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" }); + } + + (async () => { + try { + // send email with user data + console.log("email"); + + // simulate delay + + await new Promise((resolve) => setTimeout(resolve, 5000)); + + // create json file with user data + + console.log("file"); + + const exportData = { + user: { + user_id: user.user_id, + username: user.username, + account_name: user.account_name, + calendar_max_future_booking_days: + user.calendar_max_future_booking_days, + calendar_min_earliest_booking_time: + user.calendar_min_earliest_booking_time, + calendar_using_primary_calendar: + user.calendar_using_primary_calendar, + language: user.language, + analytics_enabled: user.analytics_enabled, + }, + }; + + fs.writeJson(`./user-profile-exports/${user.user_id}.json`, exportData); + + // send email with file + + channel.sendToQueue( + process.env.RABBITMQ_MAIL_QUEUE as string, + Buffer.from( + JSON.stringify({ + m: "test@roese.dev", // UserMail + t: "userAccountExportFinish", // TemplateId + l: "de", // LanguageId + // BodyData + b: { + accountExportDownloadUrl: `${ + process.env.ACCOUNT_EXPORT_URL as string + }${user.user_id}`, + }, + }) + ) + ); + } catch (error) { + logger.error(error); + } + })(); + + res.status(200).json({}); + } catch (error) { + logger.error(error); + res.status(500).send({ err: "invalid request" }); + } +} + +export function GetExportedUserAccount(req: Request, res: Response) { + try { + const { userId } = req.params; + + if (!userId) { + return res.status(400).send({ err: "invalid request" }); + } + + const file = `./user-profile-exports/${userId}.json`; + + res.download(file, (err) => { + if (err) { + logger.error(err); + res.status(500).send({ err: "invalid request" }); + } + }); + } catch (error) { + logger.error(error); + res.status(500).send({ err: "invalid request" }); + } +} diff --git a/src/rabbitmq/rabbitmq.ts b/src/rabbitmq/rabbitmq.ts new file mode 100644 index 0000000..4a407ba --- /dev/null +++ b/src/rabbitmq/rabbitmq.ts @@ -0,0 +1,28 @@ +import amqplib from "amqplib"; + +export let channel: amqplib.Channel; + +async function connect( + RABBITMQ_USERNAME: string, + RABBITMQ_PASSWORD: string, + RABBITMQ_HOST: string, + RABBITMQ_PORT: string +) { + const open = await amqplib.connect( + `amqp://${RABBITMQ_USERNAME}:${RABBITMQ_PASSWORD}@${RABBITMQ_HOST}:${RABBITMQ_PORT}` + ); + + if (!open) { + console.log("RabbitMQ connection failed"); + return; + } + + channel = await open.createChannel(); + + if (!channel) { + console.log("RabbitMQ channel failed"); + return; + } +} + +export default connect; diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index 633f10f..74dbb59 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -39,5 +39,11 @@ router.delete( sessionProtection, userController.DeleteUserProfile ); +router.post( + "/profile/export", + sessionProtection, + userController.ExportUserAccount +); +router.get("/profile/export/:id", userController.GetExportedUserAccount); export default router; diff --git a/src/validator/validator.ts b/src/validator/validator.ts index 8405a83..b81d91e 100644 --- a/src/validator/validator.ts +++ b/src/validator/validator.ts @@ -63,6 +63,11 @@ 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; } diff --git a/user-profile-exports/4417984c-de81-4930-8ae7-9f178d98ddf7.json b/user-profile-exports/4417984c-de81-4930-8ae7-9f178d98ddf7.json new file mode 100644 index 0000000..4ab971d --- /dev/null +++ b/user-profile-exports/4417984c-de81-4930-8ae7-9f178d98ddf7.json @@ -0,0 +1 @@ +{"user":{"user_id":"4417984c-de81-4930-8ae7-9f178d98ddf7","username":"Alexander","account_name":"alex","calendar_max_future_booking_days":13,"calendar_min_earliest_booking_time":8,"calendar_using_primary_calendar":true,"language":"de","analytics_enabled":false}}