From 9c294ab961012b2ee4a7c871ae372e93e302561b Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Fri, 12 Mar 2021 11:03:08 +1100 Subject: [PATCH] Updated README.md and configure-script.js --- README.md | 143 ++++++++---------- client/components/app.jsx | 2 +- client/components/pages/login.jsx | 24 +++- client/components/pages/signup.jsx | 4 +- client/markdown/credits.md | 1 - client/markdown/privacy-policy.md | 1 - configure-script.js | 224 ++++++++++++++++++----------- docs/setup-tutorial.md | 2 - server/database/models/index.js | 2 +- sql/create_database.sql | 2 - 10 files changed, 231 insertions(+), 174 deletions(-) diff --git a/README.md b/README.md index b2504b2..7675852 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,84 @@ -//TODO: update this README - # MERN-template -A website template using the MERN stack. +A website template using the MERN stack. The primary technology involved is: -# Setup Development +* React +* Nodejs +* MariaDB (with Sequelize) +* Docker -To set up this template, please ensure mariadb is running on the host computer, and run `npm install` as normal. +This template is designed to support the development of persistent browser based games (PBBGs), but it, and it's component microservices, can be used elsewhere. -1. Run `sql/create_database.sql` -2. Run `cp .envdev .env` and enter your details into the new file -3. Execute `npm run dev` - -This should get the template working in development mode. - -# Setup Deployment - -Eventually, a clean install will be this easy: - -``` -git clone https://github.com/krgamestudios/MERN-template.git -npm run configure -docker-compose up --build -``` +This template is released under the zlib license (see LICENSE). # Microservices -There are external components to this template referred to as "microservices". These can be omitted entirely by simply removing the React component that accesses them. +There are external components to this template referred to as "microservices". These can be omitted entirely by simply removing the React components that access them. These are also available via [docker hub](https://hub.docker.com/u/krgamestudios). -* News Server: https://github.com/krgamestudios/news-server * Auth Server: https://github.com/krgamestudios/auth-server +* News Server: https://github.com/krgamestudios/news-server * Chat Server: https://github.com/krgamestudios/chat-server -# TODO list +# Setup Deployment -- Account system - - A separate authentication server -- Administration Panel - - inspect aggregate user data - - Moderation tools for banning, suspending, or chat-banning users -- Chat system (microservice) - - Based on usernames - - Chat logs - - Custom emoji - - Moderation tools - - Global chat channels - - User-created chat channels with an invite/search system - - Private messages - - Icons/roles next to username -- Better compression for client files -- Some form of bot/alt account detection? -- A payment system -- Backend for energy systems -- Backend for trading and leaderboards -- inventory -- stats -- shop -- currency -- random events -- Dcumentation and tutorials -- Server-side actions to allow offline progress or progress on spotty internet connections -- Full tutorial for setting up and using the site - - Start here page - - Security holes - - HTTPS - - Default admin account - - Information about legal requirements of the developers using this template - - Privacy policy & data collection notices +A clean install is this easy: -# DONE list +``` +git clone https://github.com/krgamestudios/MERN-template.git +node run configure-script.js +docker-compose up --build +``` -- Legal Requirements: - - ~~Physical Mailing Address Config (for emails)~~ - - ~~Opt-out option (for emails)~~ - - ~~Privacy policy & data collection notices~~ - - ~~LICENSE file~~ - - ~~annoying "This site uses cookies" message~~ -- Account system - - ~~sign up~~ - - ~~validate email~~ - - ~~login (with cookies)~~ - - ~~logout (with cookies)~~ - - ~~account deletion~~ - - ~~Change passwords~~ -- Administration Panel - - ~~Default admin account~~ - - ~~Exclusive to admin accounts~~ -- News blog system (microservice) - - ~~build the microservice to provide the news feed~~ - - ~~access an external news feed~~ - - ~~admin panel for publishing and editing news~~ - - ~~"created at" and "updated at" in the response~~ -- Configuraton Script: +# Setup Development + +To set up this template in development mode: + +1. Ensure mariadb is running in your development environment +2. Run `mariadb sql/create_database.sql` as the root user +3. Run `npm install` +4. Run `cp .envdev .env` and enter your details into the `.env` file +5. Execute `npm run dev` +6. Navigate to `http://localhost:3001` in your web browser + +# Features List + +- Fully Featured Account System + - Email validation + - Logging in and out + - Account deletion + - Password management +- Fully Featured Administration Panel + - A default admin account (if desired) +- News Blog + - Optional microservice + - Secure publishing and editing of articles +- Easy To Use Configuraton Script: - ~~Default UUID keys~~ - ~~Docker, docker, docker.~~ -# Email settings +# Coming Soon -Some of the external requirements can be tricky, so let me outline what is needed. If you decide to use gmail as your email provider, then use the following `.env` settings: +- Full documentation + - Setup tutorial +- Fully Featured Chat System + - Optional microservice + - Chat logs + - Custom emoji + - Global and room-based chat + - Private messaging? + - Broadcasting to all channels + - Badges next to usernames? +- Moderation tools for banning, suspending, or chat-banning users + +# Coming Eventually +- Better compression for client files +- Backend for energy systems +- Backend for leaderboards +- Backend for items, shops, trading and currency + +# Gmail Email Settings + +If you decide to use gmail as your email provider (as I do), then use the following `.env` settings: MAIL_SMTP=smtp.gmail.com MAIL_USERNAME=you@gmail.com diff --git a/client/components/app.jsx b/client/components/app.jsx index f626eae..33967f7 100644 --- a/client/components/app.jsx +++ b/client/components/app.jsx @@ -7,7 +7,7 @@ import LazyRoute from './lazy-route'; import Markdown from './panels/markdown'; //styling -//TODO: styling import +//import a styling template here //common components import Header from './panels/header.jsx'; diff --git a/client/components/pages/login.jsx b/client/components/pages/login.jsx index c8b85a5..9bb6a31 100644 --- a/client/components/pages/login.jsx +++ b/client/components/pages/login.jsx @@ -3,6 +3,8 @@ import { Redirect } from 'react-router-dom'; import { TokenContext } from '../utilities/token-provider'; +const validateEmail = require('../../../common/utilities/validate-email'); + const LogIn = props => { //context const authTokens = useContext(TokenContext); @@ -55,7 +57,13 @@ const LogIn = props => { //DOCS: returns two values: err and authTokens const handleSubmit = async (email, password) => { - email = email.trim(); //TODO: validate email on login + email = email.trim(); + + const err = handleValidation(email, password); + + if (err) { + return [err, false]; + } //send to the auth server const result = await fetch(`${process.env.AUTH_URI}/login`, { @@ -82,4 +90,18 @@ const handleSubmit = async (email, password) => { return [null, newTokens]; }; +//returns an error message, or null on success +const handleValidation = (email, password) => { + if (!validateEmail(email)) { + return 'invalid email'; + } + + if (password.length < 8) { + return 'invalid password (Must be at least 8 characters long)'; + } + + return null; +}; + + export default LogIn; \ No newline at end of file diff --git a/client/components/pages/signup.jsx b/client/components/pages/signup.jsx index 905e389..7632465 100644 --- a/client/components/pages/signup.jsx +++ b/client/components/pages/signup.jsx @@ -4,8 +4,8 @@ import { Redirect } from 'react-router-dom'; import { TokenContext } from '../utilities/token-provider'; //utilities -const validateEmail = require('../../../common/utilities/validate-email.js'); -const validateUsername = require('../../../common/utilities/validate-username.js'); +const validateEmail = require('../../../common/utilities/validate-email'); +const validateUsername = require('../../../common/utilities/validate-username'); const SignUp = props => { //context diff --git a/client/markdown/credits.md b/client/markdown/credits.md index 99f9d3f..c48c416 100644 --- a/client/markdown/credits.md +++ b/client/markdown/credits.md @@ -4,4 +4,3 @@ MERN Template developed by Kayne Ruse, KR Game Studios [https://github.com/krgamestudios/MERN-template](https://github.com/krgamestudios/MERN-template) -TODO: generate the credits using config script \ No newline at end of file diff --git a/client/markdown/privacy-policy.md b/client/markdown/privacy-policy.md index 05ae3d6..c6e8ae9 100644 --- a/client/markdown/privacy-policy.md +++ b/client/markdown/privacy-policy.md @@ -1,3 +1,2 @@ # Privacy Policy -TODO: generate the privacy policy using config script \ No newline at end of file diff --git a/configure-script.js b/configure-script.js index dd5ac3a..2ab4e2b 100644 --- a/configure-script.js +++ b/configure-script.js @@ -1,5 +1,3 @@ -//TODO: update this file - //setup const readline = require('readline'); const fs = require('fs'); @@ -14,52 +12,92 @@ const rl = readline.createInterface({ }); //manually promisify this (util didn't work) -const question = (prompt, def) => { +const question = (prompt, def = null) => { return new Promise((resolve, reject) => { - rl.question(`${prompt} (${def}): `, answer => { - resolve(answer || def); + rl.question(`${prompt}${def ? ` (${def})` : ''}: `, answer => { + //loop on required + if (def === null && !answer) { + return resolve(question(prompt, def)); + } + + return resolve(answer || def); }); }); }; //questions (async () => { + console.log( +`This configure script will generate the following files: + +* docker-compose.yml +* Dockerfile +* startup.sql + +Currently, all microservices are mandatory; you'll have to mess with the result +and the source code if you wish to be more selective. Microservices currently +impelented are: + +* auth-server +* news-server + +See https://github.com/krgamestudios/MERN-template/wiki for help. +` +); + //project configuration const projectName = await question('Project Name', 'template'); const projectWebAddress = await question('Project Web Address', 'example.com'); - const projectMailSMTP = await question('Project Mail SMTP', 'smtp.example.com'); - const projectMailUser = await question('Project Mail Username', 'foobar@example.com'); - const projectMailPass = await question('Project Mail Password', 'foobar'); - const projectMailPhysical = await question('Project Physical Mailing Address', ''); - const projectDBUser = await question('Project Database Username', projectName); - const projectDBPass = await question('Project Database Password', 'pikachu'); + + const projectDBUser = await question('Project DB Username', projectName); + const projectDBPass = await question('Project DB Password', 'pikachu'); //news configuration const newsName = await question('News Name', 'news'); - const newsWebAddress = await question('News Web Address', 'news.example.com'); - const newsDBUser = await question('News Database Username', newsName); - const newsDBPass = await question('News Database Password', 'charizard'); - const newsKey = await question('News Query Key', uuid()); + const newsWebAddress = await question('News Web Address', `${newsName}.${projectWebAddress}`); + const newsDBUser = await question('News DB Username', newsName); + const newsDBPass = await question('News DB Password', 'charizard'); - //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()); + //auth configuration + const authName = await question('Auth Name', 'auth'); + const authWebAddress = await question('Auth Web Address', `${authName}.${projectWebAddress}`); + const authDBUser = await question('Auth DB Username', authName); + const authDBPass = await question('Auth DB Password', 'venusaur'); + + const emailSMTP = await question('Email SMTP', 'smtp.example.com'); + const emailUser = await question('Email Address', 'foobar@example.com'); + const emailPass = await question('Email Password', 'foobar'); + const emailPhysical = await question('Physical Mailing Address', ''); + + //chat goes here //database configuration - const databaseRootPassword = await question('Database Root Password', 'password'); - const databaseTimeZone = await question('Database Timezone', 'Australia/Sydney'); + const dbRootPassword = await question('Database Root Password', 'password'); + const dbTimeZone = await question('Database Timezone', 'Australia/Sydney'); + + //joint configuration + const accessToken = await question('Access Token Secret', uuid(32)); + const refreshToken = await question('Refresh Token Secret', uuid(32)); + + console.log('--Leave "Default User" blank if you don\'t want one--'); + const defaultUser = await question('Default Admin User', ''); + + //MUST be at least 8 chars + let tmpPass = ''; + while (defaultUser && tmpPass.length < 8) { + console.log('--All passwords must be at least 8 characters long--'); + tmpPass = await question('Default Admin Pass', ''); + } + const defaultPass = tmpPass; //traefic configuration - const supportEmail = await question('Support Email', projectMailUser); + const supportEmail = await question('Support Email', emailUser); - //other random values - const sessionSecret = uuid(); //for session randomness - const sessionAdmin = uuid(128); //for checking if user is admin - - //TODO: Implement chat-server as a docker container + //misc. configuration + const projectPort = 3000; + const newsPort = 3100; + const authPort = 3200; + //const chatPort = 3300; const ymlfile = ` version: "3.6" @@ -67,33 +105,24 @@ services: ${projectName}: build: . ports: - - "3000" + - "${projectPort}" labels: - - "traefik.enable=true" - - "traefik.http.routers.${projectName}router.rule=Host(\`${projectWebAddress}\`)" - - "traefik.http.routers.${projectName}router.entrypoints=websecure" - - "traefik.http.routers.${projectName}router.tls.certresolver=myresolver" - - "traefik.http.routers.${projectName}router.service=${projectName}service@docker" - - "traefik.http.services.${projectName}service.loadbalancer.server.port=3000" + - traefik.enable=true + - traefik.http.routers.${projectName}router.rule=Host(\`${projectWebAddress}\`) + - traefik.http.routers.${projectName}router.entrypoints=websecure + - traefik.http.routers.${projectName}router.tls.certresolver=myresolver + - traefik.http.routers.${projectName}router.service=${projectName}service@docker + - traefik.http.services.${projectName}service.loadbalancer.server.port=${projectPort} environment: - - WEB_PROTOCOL=https - - WEB_ADDRESS=${projectWebAddress} - WEB_PORT=3000 - - MAIL_SMTP=${projectMailSMTP} - - MAIL_USERNAME=${projectMailUser} - - MAIL_PASSWORD=${projectMailPass} - - MAIL_PHYSICAL=${projectMailPhysical} - DB_HOSTNAME=database - DB_DATABASE=${projectName} - DB_USERNAME=${projectDBUser} - DB_PASSWORD=${projectDBPass} - - DB_TIMEZONE=${databaseTimeZone} - - SESSION_SECRET=${sessionSecret} - - SESSION_ADMIN=${sessionAdmin} + - DB_TIMEZONE=${dbTimeZone} - NEWS_URI=https://${newsWebAddress}/news - - NEWS_KEY=${newsKey} - - CHAT_URI=https://${chatWebAddress}/chat - - CHAT_KEY=${chatKey} + - AUTH_URI=https://${authWebAddress}/auth + - SECRET_ACCESS=${accessToken} networks: - app-network depends_on: @@ -103,64 +132,96 @@ services: ${newsName}: image: krgamestudios/news-server:latest ports: - - "3100" + - ${newsPort} labels: - - "traefik.enable=true" - - "traefik.http.routers.${newsName}router.rule=Host(\`${newsWebAddress}\`)" - - "traefik.http.routers.${newsName}router.entrypoints=websecure" - - "traefik.http.routers.${newsName}router.tls.certresolver=myresolver" - - "traefik.http.routers.${newsName}router.service=${newsName}service@docker" - - "traefik.http.services.${newsName}service.loadbalancer.server.port=3100" + - traefik.enable=true + - traefik.http.routers.${newsName}router.rule=Host(\`${newsWebAddress}\`) + - traefik.http.routers.${newsName}router.entrypoints=websecure + - traefik.http.routers.${newsName}router.tls.certresolver=myresolver + - traefik.http.routers.${newsName}router.service=${newsName}service@docker + - traefik.http.services.${newsName}service.loadbalancer.server.port=3100 environment: - WEB_PORT=3100 - DB_HOSTNAME=database - DB_DATABASE=${newsName} - DB_USERNAME=${newsDBUser} - DB_PASSWORD=${newsDBPass} - - DB_TIMEZONE=${databaseTimeZone} + - DB_TIMEZONE=${dbTimeZone} - QUERY_LIMIT=10 - - QUERY_KEY=${newsKey} + - SECRET_ACCESS=${accessToken} networks: - app-network depends_on: - database - traefik + ${authName}: + image: krgamestudios/news-server:latest + ports: + - ${authPort} + labels: + - traefik.enable=true + - traefik.http.routers.${authName}router.rule=Host(\`${authWebAddress}\`) + - traefik.http.routers.${authName}router.entrypoints=websecure + - traefik.http.routers.${authName}router.tls.certresolver=myresolver + - traefik.http.routers.${authName}router.service=${authName}service@docker + - traefik.http.services.${authName}service.loadbalancer.server.port=${authPort} + environment: + - WEB_PROTOCOL=https + - WEB_ADDRESS=${authWebAddress} + - WEB_PORT=${authPort} + - DB_HOSTNAME=database + - DB_DATABASE=${authName} + - DB_USERNAME=${authDBUser} + - DB_PASSWORD=${authDBPass} + - DB_TIMEZONE=${dbTimeZone} + - MAIL_SMTP=${emailSMTP} + - MAIL_USERNAME=${emailUser} + - MAIL_PASSWORD=${emailPass} + - MAIL_PHYSICAL=${emailPhysical} + - ADMIN_DEFAULT_USERNAME=${defaultUser} + - ADMIN_DEFAULT_PASSWORD=${defaultPass} + - SECRET_ACCESS=${accessToken} + - SECRET_REFRESH=${refreshToken} + networks: + - app-network + depends_on: + - database + - traefik + #chat: # image: krgamestudios/chat-server - # ports: - # - "3200:3200" database: image: mariadb restart: always environment: - MYSQL_ROOT_PASSWORD: ${databaseRootPassword} + MYSQL_ROOT_PASSWORD: ${dbRootPassword} volumes: - - "./mysql:/var/lib/mysql" - - "./startup.sql:/docker-entrypoint-initdb.d/startup.sql:ro" + - ./mysql:/var/lib/mysql + - ./startup.sql:/docker-entrypoint-initdb.d/startup.sql:ro networks: - app-network traefik: - image: "traefik:v2.4" - container_name: "traefik" + image: traefik:v2.4 + container_name: traefik command: - - "--log.level=ERROR" - - "--api.insecure=false" - - "--providers.docker=true" - - "--providers.docker.exposedbydefault=false" - - "--entrypoints.websecure.address=:443" - - "--certificatesresolvers.myresolver.acme.tlschallenge=true" - - "--certificatesresolvers.myresolver.acme.email=${supportEmail}" - - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json" - - " traefik.docker.network=app-network" + - --log.level=ERROR + - --api.insecure=false + - --providers.docker=true + - --providers.docker.exposedbydefault=false + - --entrypoints.websecure.address=:443 + - --certificatesresolvers.myresolver.acme.tlschallenge=true + - --certificatesresolvers.myresolver.acme.email=${supportEmail} + - --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json + - traefik.docker.network=app-network ports: - - "80:80" - - "443:443" + - 80:80 + - 443:443 volumes: - - "./letsencrypt:/letsencrypt" - - "/var/run/docker.sock:/var/run/docker.sock:ro" + - ./letsencrypt:/letsencrypt + - /var/run/docker.sock:/var/run/docker.sock:ro networks: - app-network @@ -175,7 +236,7 @@ WORKDIR "/app" COPY package*.json ./ RUN npm install COPY . /app -EXPOSE 3000 +EXPOSE ${projectPort} ENTRYPOINT ["bash", "-c"] CMD ["sleep 10 && npm start"] `; @@ -189,9 +250,9 @@ 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}'@'%'; +CREATE DATABASE IF NOT EXISTS ${authName}; +CREATE USER IF NOT EXISTS '${authDBUser}'@'%' IDENTIFIED BY '${authDBPass}'; +GRANT ALL PRIVILEGES ON ${authName}.* TO '${authDBUser}'@'%'; FLUSH PRIVILEGES; `; @@ -203,4 +264,3 @@ FLUSH PRIVILEGES; .then(() => rl.close()) .catch(e => console.error(e)) ; - diff --git a/docs/setup-tutorial.md b/docs/setup-tutorial.md index 1cca6e0..5986293 100644 --- a/docs/setup-tutorial.md +++ b/docs/setup-tutorial.md @@ -1,5 +1,3 @@ -//TODO: move this to the wiki? - # Setup Tutorial Last Updated February 15th 2021 diff --git a/server/database/models/index.js b/server/database/models/index.js index ae594c7..109c599 100644 --- a/server/database/models/index.js +++ b/server/database/models/index.js @@ -1,3 +1,3 @@ module.exports = { - //TODO: models + //import models } \ No newline at end of file diff --git a/sql/create_database.sql b/sql/create_database.sql index 5bdf17f..d064325 100644 --- a/sql/create_database.sql +++ b/sql/create_database.sql @@ -1,5 +1,3 @@ -#TODO: move this into configure-script.js - #This file only needs to be run once, during initial development setup #This file isnt needed for actual deployment