diff --git a/.envdev b/.envdev index e80cfe3..bbe2881 100644 --- a/.envdev +++ b/.envdev @@ -13,5 +13,7 @@ DB_USERNAME=template DB_PASSWORD=pikachu DB_TIMEZONE=Australia/Sydney +CHAT_URI=http://example.com:3200/chat + SESSION_SECRET=secret SESSION_ADMIN=adminsecret \ No newline at end of file diff --git a/client/components/app.jsx b/client/components/app.jsx index 7e1d2b6..5e66b85 100644 --- a/client/components/app.jsx +++ b/client/components/app.jsx @@ -43,6 +43,7 @@ const App = props => { import('./pages/signup')} /> import('./pages/login')} /> import('./pages/account')} /> + import('./pages/chat')} /> import('./pages/admin')} /> diff --git a/client/components/pages/chat.jsx b/client/components/pages/chat.jsx new file mode 100644 index 0000000..13494c6 --- /dev/null +++ b/client/components/pages/chat.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import { useCookies } from 'react-cookie'; + +import Chat from '../panels/chat'; + +//Temporary chat page +const ChatPage = props => { + const [cookies, setCookie] = useCookies(); + + //check for logged in redirect + if (!cookies['loggedin']) { + return ; + } + + return ( +
+ +
+ ); +}; + +export default ChatPage; \ No newline at end of file diff --git a/client/components/pages/homepage.jsx b/client/components/pages/homepage.jsx index fb1112c..28380c0 100644 --- a/client/components/pages/homepage.jsx +++ b/client/components/pages/homepage.jsx @@ -3,7 +3,6 @@ import React from 'react'; import NewsFeed from '../panels/news-feed'; const HomePage = props => { - //TODO: move the URIs into the config files return (

This is the MERN template homepage.

diff --git a/client/components/panels/chat.jsx b/client/components/panels/chat.jsx new file mode 100644 index 0000000..0ebdee3 --- /dev/null +++ b/client/components/panels/chat.jsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { useCookies } from 'react-cookie'; + +const Chat = props => { + requestPseudonym(); + + return ( +
+

Chat URI: {props.uri}

+

Chat Paragraph TODO

+
+ ); +}; + +const requestPseudonym = () => { + const [cookies, setCookie] = useCookies(); + + //if your username hasn't been reserved + if (!cookies['pseudonym']) { + fetch('/api/chat/reserve', { method: 'POST' }) + .then(msg => msg.json()) + .then(json => { + if (!json.ok) { //I don't like doing this + console.error(json.error); + } + }) + .catch(e => console.error(e)) + ; + } +}; + +export default Chat; \ No newline at end of file diff --git a/configure-script.js b/configure-script.js index 43ac555..179119e 100644 --- a/configure-script.js +++ b/configure-script.js @@ -39,7 +39,12 @@ const question = (prompt, def) => { const newsDBPass = await question('News Database Password', 'charizard'); const newsKey = await question('News Query Key', uuid()); - //TODO: chat configuration + //chat configuration + const chatName = await question('Chat Name', 'chat'); + const chatWebAddress = await question('Chat Web Address', 'chat.example.com'); + const chatDBUser = await question('Chat Database Username', chatName); + const chatDBPass = await question('Chat Database Password', 'blastoise'); + const chatKey = await question('Chat Reservation Key', uuid()); //database configuration const databaseRootPassword = await question('Database Root Password', 'password'); @@ -52,6 +57,8 @@ const question = (prompt, def) => { const sessionSecret = uuid(); //for session randomness const sessionAdmin = uuid(128); //for checking if user is admin + //TODO: Implement chat-server as a docker container + const yml = ` version: "3.6" services: @@ -83,6 +90,8 @@ services: - SESSION_ADMIN=${sessionAdmin} - NEWS_URI=https://${newsWebAddress}/news - NEWS_KEY=${newsKey} + - CHAT_URI=https://${chatWebAddress}/chat + - CHAT_KEY=${chatKey} networks: - app-network depends_on: @@ -180,6 +189,10 @@ CREATE DATABASE IF NOT EXISTS ${newsName}; CREATE USER IF NOT EXISTS '${newsDBUser}'@'%' IDENTIFIED BY '${newsDBPass}'; GRANT ALL PRIVILEGES ON ${newsName}.* TO '${newsDBUser}'@'%'; +CREATE DATABASE IF NOT EXISTS ${chatName}; +CREATE USER IF NOT EXISTS '${chatDBUser}'@'%' IDENTIFIED BY '${chatDBPass}'; +GRANT ALL PRIVILEGES ON ${chatName}.* TO '${chatDBUser}'@'%'; + FLUSH PRIVILEGES; `; diff --git a/package-lock.json b/package-lock.json index eabbb14..fa62eec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,8 +18,10 @@ "express": "^4.17.1", "express-formidable": "^1.2.0", "express-session": "^1.17.1", + "form-data": "^4.0.0", "mariadb": "^2.5.2", "node-cron": "^2.0.3", + "node-fetch": "^2.6.1", "nodemailer": "^6.4.17", "react-cookie": "^4.0.3", "react-dropdown-select": "^4.7.3", @@ -1949,6 +1951,11 @@ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", "dev": true }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, "node_modules/atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -2670,6 +2677,17 @@ "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -3376,6 +3394,14 @@ "node": ">=6" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/denque": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", @@ -4319,6 +4345,19 @@ "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/formidable": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", @@ -6547,6 +6586,14 @@ "node": ">=6.0.0" } }, + "node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/node-forge": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", @@ -12384,6 +12431,11 @@ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -12983,6 +13035,14 @@ "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -13555,6 +13615,11 @@ "rimraf": "^2.6.3" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, "denque": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", @@ -14354,6 +14419,16 @@ "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "formidable": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", @@ -16100,6 +16175,11 @@ "tz-offset": "0.0.1" } }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "node-forge": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", diff --git a/package.json b/package.json index 58d8970..20295c0 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,10 @@ "express": "^4.17.1", "express-formidable": "^1.2.0", "express-session": "^1.17.1", + "form-data": "^4.0.0", "mariadb": "^2.5.2", "node-cron": "^2.0.3", + "node-fetch": "^2.6.1", "nodemailer": "^6.4.17", "react-cookie": "^4.0.3", "react-dropdown-select": "^4.7.3", diff --git a/server/accounts/logout.js b/server/accounts/logout.js index 92f0cd5..ee4a2a1 100644 --- a/server/accounts/logout.js +++ b/server/accounts/logout.js @@ -3,6 +3,7 @@ const route = (req, res) => { req.session.account = null; res.clearCookie('loggedin'); res.clearCookie('admin'); + res.clearCookie('pseudonym'); return res.status(200).end(); }; diff --git a/server/chat/index.js b/server/chat/index.js new file mode 100644 index 0000000..7d9178c --- /dev/null +++ b/server/chat/index.js @@ -0,0 +1,7 @@ +const express = require('express'); +const router = express.Router(); + +//reserve the name on the chat server (then get out of the way) +router.post('/reserve', require('./reserve')); + +module.exports = router; diff --git a/server/chat/reserve.js b/server/chat/reserve.js new file mode 100644 index 0000000..82faf63 --- /dev/null +++ b/server/chat/reserve.js @@ -0,0 +1,23 @@ +const fetch = require('node-fetch'); +const FormData = require('form-data'); + +const route = async (req, res) => { + //build the fake form data object + let form = new FormData(); + form.append('username', req.session?.account?.username); + + try { + //reserve the UUID with the chat server (hop 1) + const result = await fetch(`http://${process.env.CHAT_URI}/reserve`, { method: 'POST', body: form }); + + const json = await result.json(); + res.cookie('pseudonym', json.pseudonym); + res.status(200).send({ ok: true }); + } catch(e) { + console.error('Chat server not found'); + res.cookie('pseudonym', '.null'); + res.status(200).send({ ok: false, error: 'Chat server not found' }); + } +}; + +module.exports = route; \ No newline at end of file diff --git a/server/server.js b/server/server.js index f5120fd..a71b041 100644 --- a/server/server.js +++ b/server/server.js @@ -31,6 +31,9 @@ app.use(session({ //account management app.use('/api/accounts', require('./accounts')); +//chat management +app.use('/api/chat', require('./chat')); + //administration app.use('/api/admin', require('./admin')); require('./admin/bookkeeper')(); //BUGFIX diff --git a/webpack.config.js b/webpack.config.js index 45d9246..91c5cf5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -51,7 +51,9 @@ module.exports = ({ production, analyzer }) => { new DefinePlugin({ 'process.env': { 'NEWS_URI': production ? `"${process.env.NEWS_URI}"` : '"http://dev-news.eggtrainer.com:3100/news"', + /* TODO: (1) NEWS_KEY needs to be set in the server, and auth'd via admin accounts, NOT embedded in the client */ 'NEWS_KEY': production ? `"${process.env.NEWS_KEY}"` : '"key"', + 'CHAT_URI': production ? `"${process.env.NEWS_URI}"` : '"http://dev-chat.eggtrainer.com:3200/chat"', } }), new CleanWebpackPlugin({