diff --git a/.env.test b/.env.test
new file mode 100644
index 0000000..dd4df7c
--- /dev/null
+++ b/.env.test
@@ -0,0 +1,37 @@
+MEDIA=/Users/luke/Testing/media
+DATA=/Users/luke/Testing/data
+MAILJET_KEY="4d2a0f6deb20a7890afee2daf595c22d"
+MAILJET_SECRET="fb74f682d68bf6b05ab626c0ac93a6da"
+STAGE=test
+TAG=1.7
+DOMAIN_NAME="openmhz.test"
+PROTOCOL="http://"
+
+
+REACT_APP_BACKEND_SERVER="${PROTOCOL}api.${DOMAIN_NAME}"
+REACT_APP_FRONTEND_SERVER="${PROTOCOL}${DOMAIN_NAME}"
+REACT_APP_SOCKET_SERVER=wss://socket.${DOMAIN_NAME}
+REACT_APP_ACCOUNT_SERVER="${PROTOCOL}account.${DOMAIN_NAME}"
+REACT_APP_ADMIN_SERVER="${PROTOCOL}admin.${DOMAIN_NAME}"
+REACT_APP_COOKIE_DOMAIN=".${DOMAIN_NAME}"
+REACT_APP_ADMIN_EMAIL=luke@robotastic.com
+REACT_APP_SITE_NAME=OpenMHz
+REACT_APP_ARCHIVE_DAYS=30
+STRIPE_PUBLISHABLE_KEY=""
+STRIPE_SECRET_KEY="sk_test_S5kfQwdyRDgKtu9FQYpPh3B3"
+BACKEND_SERVER="${PROTOCOL}api.${DOMAIN_NAME}"
+FRONTEND_SERVER="${PROTOCOL}${DOMAIN_NAME}"
+ADMIN_SERVER="${PROTOCOL}admin.${DOMAIN_NAME}"
+SOCKET_SERVER="wss://socket.${DOMAIN_NAME}"
+ACCOUNT_SERVER="${PROTOCOL}account.${DOMAIN_NAME}"
+COOKIE_DOMAIN=".${DOMAIN_NAME}"
+SITE_NAME="OpenMHz"
+ADMIN_EMAIL="luke@robotastic.com"
+S3_PROFILE='wasabi-account'
+S3_ENPOINT='s3.us-west-1.wasabisys.com'
+FREE_PLAN=0
+PRO_PLAN=10
+FREE_PLAN_PRICE=0
+PRO_PLAN_PRICE=15
+FREE_PLAN_ARCHIVE=7
+PRO_PLAN_ARCHIVE=30
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 317d411..31cc58b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
prod.env
test.env
account/.eslintcache
+account/.eslintcache
diff --git a/account/.dockerignore b/account/.dockerignore
index 30bc162..563ba71 100644
--- a/account/.dockerignore
+++ b/account/.dockerignore
@@ -1 +1,2 @@
-/node_modules
\ No newline at end of file
+/node_modules
+/build
\ No newline at end of file
diff --git a/account/.eslintcache b/account/.eslintcache
index ca9e728..732a49e 100644
--- a/account/.eslintcache
+++ b/account/.eslintcache
@@ -1 +1 @@
-[{"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/index.js":"1","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/redux-router/reducers.js":"2","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/App/NavigationContainer.js":"3","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/user-actions.js":"4","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/App/Navigation.js":"5","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/redux-router/configureStore.js":"6","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/user-reducer.js":"7","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/Login.js":"8","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/query-string.js":"9","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/LoginContainer.js":"10","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/UserForm.js":"11","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/RegisterContainer.js":"12","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/SendResetPasswordContainer.js":"13","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/SendResetPassword.js":"14","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ConfirmEmail.js":"15","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ConfirmEmailContainer.js":"16","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/WaitConfirmEmailContainer.js":"17","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/SentConfirmEmail.js":"18","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ProfileContainer.js":"19","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/App/Restricted.js":"20","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/App/App.js":"21","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/SentConfirmEmailContainer.js":"22","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ResetPasswordContainer.js":"23","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/TermsContainer.js":"24","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/Register.js":"25","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/Profile.js":"26","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/WaitConfirmEmail.js":"27","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/user-constants.js":"28","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ResetPassword.js":"29","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/Terms.js":"30"},{"size":2128,"mtime":1610891944991,"results":"31","hashOfConfig":"32"},{"size":333,"mtime":1609904150827,"results":"33","hashOfConfig":"32"},{"size":570,"mtime":1609902896319,"results":"34","hashOfConfig":"32"},{"size":10143,"mtime":1609987516024,"results":"35","hashOfConfig":"32"},{"size":1489,"mtime":1609987532309,"results":"36","hashOfConfig":"32"},{"size":759,"mtime":1609902457124,"results":"37","hashOfConfig":"32"},{"size":3424,"mtime":1609902858959,"results":"38","hashOfConfig":"32"},{"size":5806,"mtime":1609987528328,"results":"39","hashOfConfig":"32"},{"size":362,"mtime":1609987504726,"results":"40","hashOfConfig":"32"},{"size":942,"mtime":1609985062605,"results":"41","hashOfConfig":"32"},{"size":8461,"mtime":1609987520749,"results":"42","hashOfConfig":"32"},{"size":273,"mtime":1609902858948,"results":"43","hashOfConfig":"32"},{"size":285,"mtime":1609902858952,"results":"44","hashOfConfig":"32"},{"size":2861,"mtime":1609986763111,"results":"45","hashOfConfig":"32"},{"size":1813,"mtime":1609987539627,"results":"46","hashOfConfig":"32"},{"size":270,"mtime":1609902858943,"results":"47","hashOfConfig":"32"},{"size":282,"mtime":1609902858956,"results":"48","hashOfConfig":"32"},{"size":692,"mtime":1609987522924,"results":"49","hashOfConfig":"32"},{"size":290,"mtime":1609902858946,"results":"50","hashOfConfig":"32"},{"size":1911,"mtime":1609987530612,"results":"51","hashOfConfig":"32"},{"size":253,"mtime":1609987534146,"results":"52","hashOfConfig":"32"},{"size":282,"mtime":1609902858953,"results":"53","hashOfConfig":"32"},{"size":273,"mtime":1609902858951,"results":"54","hashOfConfig":"32"},{"size":250,"mtime":1609902858954,"results":"55","hashOfConfig":"32"},{"size":1598,"mtime":1609987526511,"results":"56","hashOfConfig":"32"},{"size":1204,"mtime":1609902858945,"results":"57","hashOfConfig":"32"},{"size":1390,"mtime":1609987518565,"results":"58","hashOfConfig":"32"},{"size":1839,"mtime":1609902858958,"results":"59","hashOfConfig":"32"},{"size":2802,"mtime":1609987524544,"results":"60","hashOfConfig":"32"},{"size":3381,"mtime":1609986995536,"results":"61","hashOfConfig":"32"},{"filePath":"62","messages":"63","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"3sdj67",{"filePath":"64","messages":"65","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"67","messages":"68","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"69","messages":"70","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"71","messages":"72","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"73","messages":"74","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"75","messages":"76","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"77","messages":"78","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"79","messages":"80","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"81","usedDeprecatedRules":"66"},{"filePath":"82","messages":"83","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"84","messages":"85","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"86","messages":"87","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"88","messages":"89","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"90","messages":"91","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"92","messages":"93","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"94","messages":"95","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"96","messages":"97","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"98","messages":"99","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"100","messages":"101","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"102","messages":"103","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"104","messages":"105","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"106","messages":"107","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"108","messages":"109","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"110","messages":"111","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"112","messages":"113","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"114","messages":"115","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"116","messages":"117","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"118","messages":"119","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"120","messages":"121","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},{"filePath":"122","messages":"123","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/index.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/redux-router/reducers.js",[],["124","125"],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/App/NavigationContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/user-actions.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/App/Navigation.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/redux-router/configureStore.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/user-reducer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/Login.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/query-string.js",["126"],"export default {\n parse: function parse(queryString) {\n var query = {};\n var pairs = (queryString[0] === '?' ? queryString.substr(1) : queryString).split('&');\n for (var i = 0; i < pairs.length; i++) {\n var pair = pairs[i].split('=');\n query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '');\n }\n return query;\n }\n}\n","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/LoginContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/UserForm.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/RegisterContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/SendResetPasswordContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/SendResetPassword.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ConfirmEmail.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ConfirmEmailContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/WaitConfirmEmailContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/SentConfirmEmail.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ProfileContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/App/Restricted.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/App/App.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/SentConfirmEmailContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ResetPasswordContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/TermsContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/Register.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/Profile.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/WaitConfirmEmail.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/user-constants.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ResetPassword.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/Terms.js",[],{"ruleId":"127","replacedBy":"128"},{"ruleId":"129","replacedBy":"130"},{"ruleId":"131","severity":1,"message":"132","line":1,"column":1,"nodeType":"133","endLine":11,"endColumn":2},"no-native-reassign",["134"],"no-negated-in-lhs",["135"],"import/no-anonymous-default-export","Assign object to a variable before exporting as module default","ExportDefaultDeclaration","no-global-assign","no-unsafe-negation"]
\ No newline at end of file
+[{"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/index.js":"1","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/redux-router/reducers.js":"2","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/App/NavigationContainer.js":"3","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/user-actions.js":"4","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/App/Navigation.js":"5","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/redux-router/configureStore.js":"6","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/user-reducer.js":"7","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/Login.js":"8","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/query-string.js":"9","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/LoginContainer.js":"10","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/UserForm.js":"11","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/RegisterContainer.js":"12","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/SendResetPasswordContainer.js":"13","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/SendResetPassword.js":"14","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ConfirmEmail.js":"15","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ConfirmEmailContainer.js":"16","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/WaitConfirmEmailContainer.js":"17","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/SentConfirmEmail.js":"18","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ProfileContainer.js":"19","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/App/Restricted.js":"20","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/App/App.js":"21","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/SentConfirmEmailContainer.js":"22","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ResetPasswordContainer.js":"23","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/TermsContainer.js":"24","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/Register.js":"25","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/Profile.js":"26","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/WaitConfirmEmail.js":"27","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/user-constants.js":"28","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ResetPassword.js":"29","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/Terms.js":"30"},{"size":2128,"mtime":1610891944991,"results":"31","hashOfConfig":"32"},{"size":333,"mtime":1609904150827,"results":"33","hashOfConfig":"32"},{"size":570,"mtime":1609902896319,"results":"34","hashOfConfig":"32"},{"size":10455,"mtime":1610977327935,"results":"35","hashOfConfig":"32"},{"size":1489,"mtime":1609987532309,"results":"36","hashOfConfig":"32"},{"size":759,"mtime":1609902457124,"results":"37","hashOfConfig":"32"},{"size":3424,"mtime":1609902858959,"results":"38","hashOfConfig":"32"},{"size":5806,"mtime":1609987528328,"results":"39","hashOfConfig":"32"},{"size":362,"mtime":1609987504726,"results":"40","hashOfConfig":"32"},{"size":942,"mtime":1609985062605,"results":"41","hashOfConfig":"32"},{"size":8461,"mtime":1609987520749,"results":"42","hashOfConfig":"32"},{"size":273,"mtime":1609902858948,"results":"43","hashOfConfig":"32"},{"size":285,"mtime":1609902858952,"results":"44","hashOfConfig":"32"},{"size":2861,"mtime":1609986763111,"results":"45","hashOfConfig":"32"},{"size":1813,"mtime":1609987539627,"results":"46","hashOfConfig":"32"},{"size":270,"mtime":1609902858943,"results":"47","hashOfConfig":"32"},{"size":282,"mtime":1609902858956,"results":"48","hashOfConfig":"32"},{"size":692,"mtime":1609987522924,"results":"49","hashOfConfig":"32"},{"size":290,"mtime":1609902858946,"results":"50","hashOfConfig":"32"},{"size":1911,"mtime":1609987530612,"results":"51","hashOfConfig":"32"},{"size":253,"mtime":1609987534146,"results":"52","hashOfConfig":"32"},{"size":282,"mtime":1609902858953,"results":"53","hashOfConfig":"32"},{"size":273,"mtime":1609902858951,"results":"54","hashOfConfig":"32"},{"size":250,"mtime":1609902858954,"results":"55","hashOfConfig":"32"},{"size":1598,"mtime":1609987526511,"results":"56","hashOfConfig":"32"},{"size":1204,"mtime":1609902858945,"results":"57","hashOfConfig":"32"},{"size":1390,"mtime":1609987518565,"results":"58","hashOfConfig":"32"},{"size":1839,"mtime":1609902858958,"results":"59","hashOfConfig":"32"},{"size":2802,"mtime":1609987524544,"results":"60","hashOfConfig":"32"},{"size":3381,"mtime":1609986995536,"results":"61","hashOfConfig":"32"},{"filePath":"62","messages":"63","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},"3sdj67",{"filePath":"65","messages":"66","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"67","messages":"68","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"69","messages":"70","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"71","messages":"72","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"73","messages":"74","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"75","messages":"76","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"77","messages":"78","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"79","messages":"80","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"81","usedDeprecatedRules":"64"},{"filePath":"82","messages":"83","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"84","messages":"85","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"86","messages":"87","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"88","messages":"89","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"90","messages":"91","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"92","messages":"93","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"94","messages":"95","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"96","messages":"97","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"98","messages":"99","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"100","messages":"101","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"102","messages":"103","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"104","messages":"105","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"106","messages":"107","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"108","messages":"109","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"110","messages":"111","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"112","messages":"113","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"114","messages":"115","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"116","messages":"117","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"118","messages":"119","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"120","messages":"121","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},{"filePath":"122","messages":"123","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"64"},"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/index.js",[],["124","125"],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/redux-router/reducers.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/App/NavigationContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/user-actions.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/App/Navigation.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/redux-router/configureStore.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/user-reducer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/Login.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/query-string.js",["126"],"export default {\n parse: function parse(queryString) {\n var query = {};\n var pairs = (queryString[0] === '?' ? queryString.substr(1) : queryString).split('&');\n for (var i = 0; i < pairs.length; i++) {\n var pair = pairs[i].split('=');\n query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '');\n }\n return query;\n }\n}\n","/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/LoginContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/UserForm.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/RegisterContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/SendResetPasswordContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/SendResetPassword.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ConfirmEmail.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ConfirmEmailContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/WaitConfirmEmailContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/SentConfirmEmail.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ProfileContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/App/Restricted.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/App/App.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/SentConfirmEmailContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ResetPasswordContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/TermsContainer.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/Register.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/Profile.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/WaitConfirmEmail.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/user-constants.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/ResetPassword.js",[],"/Users/luke/Projects/OpenMHz/trunk-server-public/account/src/User/Terms.js",[],{"ruleId":"127","replacedBy":"128"},{"ruleId":"129","replacedBy":"130"},{"ruleId":"131","severity":1,"message":"132","line":1,"column":1,"nodeType":"133","endLine":11,"endColumn":2},"no-native-reassign",["134"],"no-negated-in-lhs",["135"],"import/no-anonymous-default-export","Assign object to a variable before exporting as module default","ExportDefaultDeclaration","no-global-assign","no-unsafe-negation"]
\ No newline at end of file
diff --git a/account/.not-env b/account/.not-env
deleted file mode 100644
index bab6502..0000000
--- a/account/.not-env
+++ /dev/null
@@ -1,12 +0,0 @@
-REACT_APP_BACKEND_SERVER=https://api.openmhz.com
-REACT_APP_FRONTEND_SERVER=https://openmhz.com
-REACT_APP_SOCKET_SERVER=wss://api.openmhz.com
-REACT_APP_ACCOUNT_SERVER=https://account.openmhz.com
-REACT_APP_ADMIN_SERVER=localhost:3000
-REACT_APP_ADMIN_EMAIL=luke@robotastic.com
-REACT_APP_SITE_NAME=OpenMHz
-REACT_APP_ARCHIVE_DAYS=30
-REACT_APP_PRO_PLAN=1
-REACT_APP_FREE_PLAN=0
-REACT_APP_PRO_PLAN_ARCHIVE=30
-REACT_APP_FREE_PLAN_ARCHIVE=7
diff --git a/account/Dockerfile b/account/Dockerfile
index 713548e..03c1f20 100644
--- a/account/Dockerfile
+++ b/account/Dockerfile
@@ -2,11 +2,19 @@
FROM node:13.12.0-alpine as build
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
+ARG REACT_APP_BACKEND_SERVER
+ARG REACT_APP_ADMIN_SERVER
+ARG REACT_APP_ACCOUNT_SERVER
+ARG REACT_APP_FRONTEND_SERVER
+ARG REACT_APP_SITE_NAME
+ARG REACT_APP_ADMIN_EMAIL
+ARG NODE_ENV
COPY package.json ./
RUN npm install
RUN npm install react-scripts@3.4.1 -g --silent
COPY . ./
RUN npm run build
+RUN env
# production environment
FROM node:13.12.0-alpine
@@ -14,6 +22,7 @@ RUN mkdir -p /app/public
COPY ./package.json /tmp
RUN cd /tmp && npm install
RUN cp -a /tmp/node_modules /app
+RUN env
WORKDIR /app
COPY server /app
COPY --from=build /app/build /app/public
diff --git a/account/server/config/express.js b/account/server/config/express.js
index 5a8f638..38da07c 100644
--- a/account/server/config/express.js
+++ b/account/server/config/express.js
@@ -6,8 +6,12 @@ const connectMongo = require("connect-mongo");
const secrets = require("./secrets");
const MongoStore = connectMongo(session)
-var cookie_domain = process.env['COOKIE_DOMAIN'] != null ? process.env['COOKIE_DOMAIN'] : '.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
-
+var cookie_domain = process.env['REACT_APP_COOKIE_DOMAIN'] != null ? process.env['REACT_APP_COOKIE_DOMAIN'] : '.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
+var backend_server = process.env['REACT_APP_BACKEND_SERVER'] != null ? process.env['REACT_APP_BACKEND_SERVER'] : 'https://api.openmhz.com';
+var frontend_server = process.env['REACT_APP_FRONTEND_SERVER'] != null ? process.env['REACT_APP_FRONTEND_SERVER'] : 'https://openmhz.com';
+var socket_server = process.env['REACT_APP_SOCKET_SERVER'] != null ? process.env['REACT_APP_SOCKET_SERVER'] : 'wss://socket.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
+var account_server = process.env['REACT_APP_ACCOUNT_SERVER'] != null ? process.env['REACT_APP_ACCOUNT_SERVER'] : 'https://account.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
+var dev_server = frontend_server + ":3000"
module.exports = function(app, passport) {
app.set("port", 3009)
@@ -22,7 +26,6 @@ module.exports = function(app, passport) {
app.use(express.static(path.join(process.cwd(), 'public')));
const sess = {
- secret: 'secret',
/*saveUninitialized: false,
resave: false,*/
resave: true,
@@ -56,5 +59,34 @@ module.exports = function(app, passport) {
app.use(passport.initialize())
app.use(passport.session())
+
+ app.use('/*', function(req, res, next) {
+ var allowedOrigins = [ account_server];
+ allowedOrigins.push(frontend_server);
+ allowedOrigins.push(backend_server);
+ allowedOrigins.push(socket_server);
+ allowedOrigins.push(dev_server);
+ var origin = req.headers.origin;
+
+
+ if (allowedOrigins.indexOf(origin) > -1) {
+ res.setHeader('Access-Control-Allow-Origin', origin);
+ } else if (req.headers["user-agent"] == 'TrunkRecorder1.0') {
+ res.setHeader('Access-Control-Allow-Origin', "*");
+ } else {
+ res.setHeader('Access-Control-Allow-Origin', "*");
+ if (origin) {
+ console.warn("forcing CORS for: " + origin + " referer: " + req.headers.referer);
+ }
+ }
+ res.header("Access-Control-Allow-Headers", "X-Requested-With");
+ res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS,WEBSOCKET');
+ res.header('Access-Control-Allow-Credentials', 'true');
+ res.header("Access-Control-Allow-Headers", "Access-Control-Allow-Headers, Access-Control-Allow-Credentials, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Max-Age");
+ res.header('Access-Control-Max-Age', '600');
+ next();
+ });
+
+
}
diff --git a/account/server/config/passport-strategies/local.js b/account/server/config/passport-strategies/local.js
index d01cdc9..21c5bc7 100644
--- a/account/server/config/passport-strategies/local.js
+++ b/account/server/config/passport-strategies/local.js
@@ -21,11 +21,15 @@ const local = new LocalStrategy({
}
}]
}, (err, user) => {
- if(!user) return done(null, false, { message: `Email ${email} not found` })
+ if(!user) {
+ console.error("Auth Error - user not found: " + email);
+ return done(null, false, { message: `Invalid email or password` })
+ }
user.comparePassword(password, (err, isMatch) => {
if (isMatch) {
return done(null, user)
} else {
+ console.error("Auth Error - password mismatch: " + email);
return done(null, false, { message: "Invalid email or password" })
}
})
diff --git a/account/server/controllers/plans.js b/account/server/controllers/plans.js
index b4d6307..7b943c6 100644
--- a/account/server/controllers/plans.js
+++ b/account/server/controllers/plans.js
@@ -7,10 +7,10 @@ const stripeKeySecret = process.env.STRIPE_SECRET_KEY;
const stripe = require("stripe")(stripeKeySecret);
-var admin_email = process.env['ADMIN_EMAIL'] != null ? process.env['ADMIN_EMAIL'] : "luke@openmhz.com";
-var site_name = process.env['SITE_NAME'] != null ? process.env['SITE_NAME'] : "OpenMHz";
-var account_server = process.env['ACCOUNT_SERVER'] != null ? process.env['ACCOUNT_SERVER'] : "https://account.openmhz.com";
-var cookie_domain = process.env['COOKIE_DOMAIN'] != null ? process.env['COOKIE_DOMAIN'] : '.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
+var admin_email = process.env['REACT_APP_ADMIN_EMAIL'] != null ? process.env['REACT_ADMIN_ADMIN_EMAIL'] : "luke@openmhz.com";
+var site_name = process.env['REACT_APP_SITE_NAME'] != null ? process.env['REACT_APP_SITE_NAME'] : "OpenMHz";
+var account_server = process.env['REACT_APP_ACCOUNT_SERVER'] != null ? process.env['REACT_APP_ACCOUNT_SERVER'] : "https://account.openmhz.com";
+var cookie_domain = process.env['REACT_APP_COOKIE_DOMAIN'] != null ? process.env['REACT_APP_COOKIE_DOMAIN'] : '.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
exports.isLoggedIn = function (req, res, next) {
if (req.isAuthenticated()) return next();
diff --git a/account/server/controllers/users.js b/account/server/controllers/users.js
index f916874..1708530 100644
--- a/account/server/controllers/users.js
+++ b/account/server/controllers/users.js
@@ -7,10 +7,10 @@ process.env['MAILJET_KEY'],
);
const crypto = require("crypto");
-var admin_email = process.env['ADMIN_EMAIL'] != null ? process.env['ADMIN_EMAIL'] : "luke@openmhz.com";
-var site_name = process.env['SITE_NAME'] != null ? process.env['SITE_NAME'] : "OpenMHz";
-var account_server = process.env['ACCOUNT_SERVER'] != null ? process.env['ACCOUNT_SERVER'] : "https://account.openmhz.com";
-var cookie_domain = process.env['COOKIE_DOMAIN'] != null ? process.env['COOKIE_DOMAIN'] : '.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
+var admin_email = process.env['REACT_APP_ADMIN_EMAIL'] != null ? process.env['REACT_APP_ADMIN_EMAIL'] : "luke@openmhz.com";
+var site_name = process.env['REACT_APP_SITE_NAME'] != null ? process.env['REACT_APP_SITE_NAME'] : "OpenMHz";
+var account_server = process.env['REACT_APP_ACCOUNT_SERVER'] != null ? process.env['REACT_APP_ACCOUNT_SERVER'] : "https://account.openmhz.com";
+var cookie_domain = process.env['REACT_APP_COOKIE_DOMAIN'] != null ? process.env['REACT_APP_COOKIE_DOMAIN'] : '.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
exports.isLoggedIn = function(req, res, next) {
if (req.isAuthenticated()) return next();
@@ -21,8 +21,7 @@ exports.isLoggedIn = function(req, res, next) {
exports.authenticated = function(req, res, next) {
res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
- console.log("auth: " + req.isAuthenticated());
- console.log("req.user: " + req.user);
+ console.log("account/server/controllers/users.js - exports.authenticated() Auth check for user: " + req.user + " is: " + req.isAuthenticated());
if (req.isAuthenticated()) {
var clientUser = (({
firstName,
@@ -42,11 +41,7 @@ exports.authenticated = function(req, res, next) {
req.user
);
clientUser.userId = req.user.id
- if (req.user.customerId) {
- clientUser.billing = true;
- } else {
- clientUser.billing = false;
- }
+
return res.json({
success: true,
user: clientUser
@@ -93,7 +88,7 @@ exports.login = function(req, res, next) {
userId: user.id
});
}
- console.log(user);
+ console.log("account/server/controllers/users.js - req.login() Authenicated: " + user.email);
// go ahead and create the new user
var clientUser = (({
firstName,
@@ -454,6 +449,7 @@ exports.terms = function(req, res, next) {
exports.validateProfile = function(req, res, next) {
+ console.log("Validating user profile: "+ req.body.email);
if (!req.body.firstName || (req.body.firstName.length < 2)) {
console.error("ERROR: Validate System - req.body.firstName");
res.json({
@@ -549,6 +545,8 @@ exports.updateProfile = function(req, res, next) {
// -------------------------------------------
exports.register = function(req, res, next) {
+ console.log("Registration request for: " + req.body.email);
+
User.findOne({
$or: [{
email: req.body.email
@@ -585,16 +583,16 @@ exports.register = function(req, res, next) {
);
user.password = req.body.password;
user.email = req.body.email;
-
+
User.create(user).then(function(dbUser) {
- console.log(dbUser);
+ console.log("Successfully registered: " + dbUser.email + ", now sending confirmation email.");
// Since regiestration worked, send a confirmation email.
return handleSendConfirmEmail(dbUser);
}).then(function(result) {
res.json(result);
return;
}).catch(function(err) {
- console.error(err);
+ console.error("Error creating user: " + user.email + " Error: " +err);
res.json({
success: false,
message: err
diff --git a/account/server/index.js b/account/server/index.js
index c7d87c7..0ed2e58 100644
--- a/account/server/index.js
+++ b/account/server/index.js
@@ -15,19 +15,44 @@ const app = express()
// -------------------------------------------
-const connect = () => {
- mongoose.connect(secrets.db, (err, res) => {
+async function connect() {
+
+ // Demonstrate the readyState and on event emitters
+ console.log(mongoose.connection.readyState); //logs 0
+ mongoose.connection.on('connecting', () => {
+ console.log('Mongoose is connecting')
+ console.log(mongoose.connection.readyState); //logs 2
+ });
+ mongoose.connection.on('connected', () => {
+ console.log('Mongoose is connected');
+ console.log(mongoose.connection.readyState); //logs 1
+ });
+ mongoose.connection.on('disconnecting', () => {
+ console.log('Mongoose is disconnecting');
+ console.log(mongoose.connection.readyState); // logs 3
+ });
+
+ // Connect to a MongoDB server running on 'localhost:27017' and use the
+ // 'test' database.
+ await mongoose.connect(secrets.db, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true }, (err, res) => {
if (err) {
- console.log(`Error connecting to ${secrets.db}. ${err}`)
+ console.log(`Mongoose - Error connecting to ${secrets.db}. ${err}`)
} else {
- console.log(`Successfully connected to ${secrets.db}.`)
+ console.log(`Mongoose Successfully connected to ${secrets.db}.`)
}
})
+ console.log("All Done");
}
-connect()
-
-mongoose.connection.on("error", console.error)
-mongoose.connection.on("disconnected", connect)
+connect();
+mongoose.connection.on('error', err => {
+ console.error("Mongoose Error!")
+ console.error(err);
+ });
+mongoose.connection.on('disconnected', () => {
+ console.log('Mongoose disconnected');
+ console.log(mongoose.connection.readyState); //logs 0
+ connect();
+});
// -------------------------------------------
diff --git a/account/server/models/user.js b/account/server/models/user.js
index 2d8b18e..08ebad7 100644
--- a/account/server/models/user.js
+++ b/account/server/models/user.js
@@ -58,6 +58,7 @@ const UserSchema = new mongoose.Schema({
/**
* Password hash middleware.
*/
+/*
UserSchema.pre("save", function(next) {
var user = this
if (!user.isModified("password")) return next()
@@ -70,11 +71,23 @@ UserSchema.pre("save", function(next) {
next()
})
})
-})
+})*/
+
+UserSchema.pre("save", function(next) {
+ if(!this.isModified("password")) {
+ return next();
+ }
+ hashed = bcrypt.hashSync(this.password, 8);
+ this.password = hashed;
+ this.local.password = hashed;
+
+ next();
+});
/*
Defining our own custom document instance method
*/
+/*
UserSchema.methods = {
comparePassword: function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.local.password, (err, isMatch) => {
@@ -82,7 +95,12 @@ UserSchema.pre("save", function(next) {
cb(null, isMatch)
})
}
- }
+ }*/
+
+ UserSchema.methods.comparePassword = function(plaintext, callback) {
+ return callback(null, bcrypt.compareSync(plaintext, this.local.password));
+};
+
/**
* Statics
diff --git a/account/src/App/Navigation.js b/account/src/App/Navigation.js
index 221748f..cd69f71 100644
--- a/account/src/App/Navigation.js
+++ b/account/src/App/Navigation.js
@@ -23,7 +23,7 @@ class Navigation extends Component {
{this.props.user.authenticated ? (