From ffe3b01e321dea4a4f9a1c36a47a350a8f1eaf49 Mon Sep 17 00:00:00 2001 From: Esther Date: Wed, 1 Jul 2020 12:10:30 +0100 Subject: [PATCH] Completed contact page?? --- app.js | 2 + package-lock.json | 116 ++++++++++++++++++++++++++++++++++ package.json | 2 + public/stylesheets/style.css | 26 +++++++- public/stylesheets/style.sass | 27 +++++++- routes/about.js | 5 +- routes/contact.js | 26 +++++--- views/about.pug | 7 +- views/contact-failure.pug | 4 -- views/contact-success.pug | 4 -- views/contact.pug | 52 ++++++++++----- 11 files changed, 229 insertions(+), 42 deletions(-) delete mode 100644 views/contact-failure.pug delete mode 100644 views/contact-success.pug diff --git a/app.js b/app.js index 1e46232..f957e98 100644 --- a/app.js +++ b/app.js @@ -5,6 +5,7 @@ var cookieParser = require('cookie-parser'); var mLogger = require('morgan'); var sassMiddleware = require('node-sass-middleware'); var logger = require('./config/winston'); +const helmet = require("helmet"); var indexRouter = require('./routes/index'); var aboutRouter = require('./routes/about'); @@ -25,6 +26,7 @@ if (process.env.NODE_ENV === 'production') { app.use(mLogger('dev')); } +app.use(helmet()); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); diff --git a/package-lock.json b/package-lock.json index c2ff963..3540072 100644 --- a/package-lock.json +++ b/package-lock.json @@ -250,6 +250,11 @@ } } }, + "bowser": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz", + "integrity": "sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -278,6 +283,11 @@ "map-obj": "^1.0.0" } }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -428,6 +438,11 @@ "safe-buffer": "5.1.2" } }, + "content-security-policy-builder": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.1.0.tgz", + "integrity": "sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ==" + }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -482,6 +497,11 @@ "assert-plus": "^1.0.0" } }, + "dasherize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz", + "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg=" + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -520,6 +540,11 @@ "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" }, + "dont-sniff-mimetype": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.1.0.tgz", + "integrity": "sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug==" + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -634,6 +659,11 @@ } } }, + "express-rate-limit": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.1.3.tgz", + "integrity": "sha512-TINcxve5510pXj4n9/1AMupkj3iWxl3JuZaWhCdYDlZeoCPqweGZrxbrlqTCFb1CT5wli7s8e2SH/Qz2c9GorA==" + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -659,6 +689,11 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" }, + "feature-policy": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz", + "integrity": "sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==" + }, "fecha": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", @@ -877,11 +912,77 @@ "resolved": "https://registry.npmjs.org/hcaptcha/-/hcaptcha-0.0.2.tgz", "integrity": "sha512-wWOncj/sY+q8s7tV12tjn3cFNoQhSu3l/7nTJi4QkFKALQi9XnduoXrV/KFzLg5lnB+5560zSAoi9YdYPDw6Eg==" }, + "helmet": { + "version": "3.23.3", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.23.3.tgz", + "integrity": "sha512-U3MeYdzPJQhtvqAVBPntVgAvNSOJyagwZwyKsFdyRa8TV3pOKVFljalPOCxbw5Wwf2kncGhmP0qHjyazIdNdSA==", + "requires": { + "depd": "2.0.0", + "dont-sniff-mimetype": "1.1.0", + "feature-policy": "0.3.0", + "helmet-crossdomain": "0.4.0", + "helmet-csp": "2.10.0", + "hide-powered-by": "1.1.0", + "hpkp": "2.0.0", + "hsts": "2.2.0", + "nocache": "2.1.0", + "referrer-policy": "1.2.0", + "x-xss-protection": "1.3.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, + "helmet-crossdomain": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz", + "integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA==" + }, + "helmet-csp": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.10.0.tgz", + "integrity": "sha512-Rz953ZNEFk8sT2XvewXkYN0Ho4GEZdjAZy4stjiEQV3eN7GDxg1QKmYggH7otDyIA7uGA6XnUMVSgeJwbR5X+w==", + "requires": { + "bowser": "2.9.0", + "camelize": "1.0.0", + "content-security-policy-builder": "2.1.0", + "dasherize": "2.0.0" + } + }, + "hide-powered-by": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.1.0.tgz", + "integrity": "sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg==" + }, "hosted-git-info": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" }, + "hpkp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz", + "integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI=" + }, + "hsts": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.2.0.tgz", + "integrity": "sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ==", + "requires": { + "depd": "2.0.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, "http-errors": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", @@ -1262,6 +1363,11 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "nocache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", + "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" + }, "node-gyp": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", @@ -1735,6 +1841,11 @@ "strip-indent": "^1.0.1" } }, + "referrer-policy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz", + "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==" + }, "repeating": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", @@ -2289,6 +2400,11 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "x-xss-protection": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.3.0.tgz", + "integrity": "sha512-kpyBI9TlVipZO4diReZMAHWtS0MMa/7Kgx8hwG/EuZLiA6sg4Ah/4TRdASHhRRN3boobzcYgFRUFSgHRge6Qhg==" + }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", diff --git a/package.json b/package.json index 982715c..aafc214 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "cookie-parser": "1.4.4", "debug": "4.1.1", "express": "4.17.1", + "express-rate-limit": "^5.1.3", "hcaptcha": "0.0.2", + "helmet": "^3.23.3", "http-errors": "1.7.3", "morgan": "1.10.0", "node-sass-middleware": "0.11.0", diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 6ad8ad1..3882368 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -25,9 +25,12 @@ h2 { font-weight: bold; font-family: "Apercu Mono", monospace; } -p { +p, label, input, textarea { font-family: "Apercu Mono", monospace; } +label { + font-size: 3vh; } + a:hover { color: #CDE7B0; } @@ -147,6 +150,27 @@ footer { .start { margin-top: 3vh; } +#contact-message { + margin-top: 2vh; } + #contact-message p { + color: #CDE7B0; } + #contact-message p span { + text-decoration: underline #CDE7B0; } + +.h-captcha { + margin-bottom: 0.5vh; } + +.btn-primary { + font-family: "Apercu Mono"; + background-color: #CC7A98; + color: #002234; + border-color: #002234; } + +.btn-primary:hover { + background-color: #002234; + color: #CC7A98; + border-color: #CC7A98; } + .ham { width: 30px; height: 20px; diff --git a/public/stylesheets/style.sass b/public/stylesheets/style.sass index bdb67bf..c455f8e 100644 --- a/public/stylesheets/style.sass +++ b/public/stylesheets/style.sass @@ -29,13 +29,15 @@ h2 font-weight: bold font-family: "Apercu Mono", monospace -p +p, label, input, textarea font-family: "Apercu Mono", monospace +label + font-size: 3vh + a:hover color: $green - article min-height: 100% display: grid @@ -166,6 +168,27 @@ footer .start margin-top: 3vh +#contact-message + margin-top: 2vh + p + color: $green + span + text-decoration: underline $green + +.h-captcha + margin-bottom: 0.5vh + +.btn-primary + font-family: "Apercu Mono" + background-color: $pink + color: $black + border-color: $black + +.btn-primary:hover + background-color: $black + color: $pink + border-color: $pink + // Nav stuff .ham diff --git a/routes/about.js b/routes/about.js index 4a7222d..8b8d5ed 100644 --- a/routes/about.js +++ b/routes/about.js @@ -4,8 +4,8 @@ var router = express.Router(); /* GET home page. */ router.get('/', function(req, res, next) { - const ghost_key = process.env.GHOST_KEY - const base_url = `https://blog.pastel.codes/ghost/api/v3/content/posts/?key=${ghost_key}` + const GHOST_KEY = process.env.GHOST_KEY + const base_url = `https://blog.pastel.codes/ghost/api/v3/content/posts/?key=${GHOST_KEY}` axios.all([ axios.get(`${base_url}&limit=3`), @@ -22,7 +22,6 @@ router.get('/', function(req, res, next) { .catch(error => { console.log(error); }); - // https://blog.pastel.codes/ghost/api/v3/content/posts/?key= }); module.exports = router; diff --git a/routes/contact.js b/routes/contact.js index e263fd8..685552f 100644 --- a/routes/contact.js +++ b/routes/contact.js @@ -1,19 +1,29 @@ var express = require('express'); +var rate_limit = require("express-rate-limit") const {verify} = require('hcaptcha'); const nodemailer = require('nodemailer') var router = express.Router(); +const contact_rate_limit = rate_limit({ + windowMs: 10 * 60 * 1000, // 10 minutes + max: 1, // limit each IP to 10 requests per windowMs + message: "Too many contact requests, try again later.", + handler: function(req, res /*, next*/) { + res.render('error', {title:"Error", message: "Too many contact requests, try again later.", error: {status: null}}) + }, +}); + // POST route from contact form -router.post('/', (req, res) => { +router.post('/', contact_rate_limit,(req, res) => { const TO_GMAIL_USER = process.env.TO_GMAIL_USER const FROM_GMAIL_USER = process.env.FROM_GMAIL_USER const GMAIL_PASS = process.env.GMAIL_PASS const HCAPTCHA_KEY = process.env.HCAPTCHA_KEY const token = req.body["g-recaptcha-response"]; + const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; verify(HCAPTCHA_KEY, token) .then((data) => { - console.log(data) if (data.success === true) { // Instantiate the SMTP server const smtpTrans = nodemailer.createTransport({ @@ -31,7 +41,7 @@ router.post('/', (req, res) => { from: 'Pastel.codes Contact Notifications', // This is ignored by Gmail to: TO_GMAIL_USER, subject: 'New message from contact form at pastel.codes', - text: `${req.body.name} (${req.body.email}) says: ${req.body.message}` + text: `${req.body.firstname} ${req.body.lastname} (${req.body.email})\nsays: ${req.body.message}\n\nip: ${ip}` } // maybe send conformation email? @@ -40,26 +50,26 @@ router.post('/', (req, res) => { smtpTrans.sendMail(mailOpts, (error, response) => { if (error) { console.log(error) - res.render('error', {message: "Email did not send"}) // Show a page indicating failure + res.render('error', { message: "Email did not send" }) // Show a page indicating failure } else { - res.render('contact-success') // Show a page indicating success + res.render('contact', { message: "I will get back to you soon!", success: "Make sure the email is from ", email: TO_GMAIL_USER }) // Show a page indicating success } }) } else { // rerender with same info in the text box and show error message - res.render('contact', { title: 'Contact', description: "Contact me!", message: "Captcha failed, try again" }); + res.render('contact', { message: "Captcha failed, try again" }); } }) .catch(error => { console.log(error); - res.render('contact', {title: 'Contact', description: "Contact me!", message: "Something wrong happened, try again later"}); + res.render('contact', { message: "Something wrong happened, try again later" }); }); }) /* GET home page. */ router.get('/', function(req, res, next) { - res.render('contact', { title: 'Contact', description: "Contact me!", message: null }); + res.render('contact', { title: 'Contact', description: "Contact me!"}); }); module.exports = router; diff --git a/views/about.pug b/views/about.pug index 6819067..17cd194 100644 --- a/views/about.pug +++ b/views/about.pug @@ -1,10 +1,10 @@ extends layout block nav-links - li.nav-item.active + li.nav-item a.nav-link(href='/') span Home - li.nav-item + li.nav-item.active a.nav-link(href='#') span About li.nav-item @@ -28,7 +28,8 @@ block content h1 span Hello. p I’m Esther, a 19 year old student in 2nd year of university, who studies computer science & artificial intelligence. - p In my free time, I create small projects to learn new skills and for them to function well for others to use; additionally, I do some graphic design / art as a hobby but has helped me create catching designs. + p In my free time, I create small projects to learn new skills to be helpful for others, recently i have been focusing on node.js. + p Graphic design is also very fun and i enjoy making small projects to post on instagram, it helps with programming too which is neat .row .col.start h1 diff --git a/views/contact-failure.pug b/views/contact-failure.pug deleted file mode 100644 index 711c469..0000000 --- a/views/contact-failure.pug +++ /dev/null @@ -1,4 +0,0 @@ -extends layout - -block content - p fuck \ No newline at end of file diff --git a/views/contact-success.pug b/views/contact-success.pug deleted file mode 100644 index 6dffe16..0000000 --- a/views/contact-success.pug +++ /dev/null @@ -1,4 +0,0 @@ -extends layout - -block content - p yay \ No newline at end of file diff --git a/views/contact.pug b/views/contact.pug index c78b28d..cb214fd 100644 --- a/views/contact.pug +++ b/views/contact.pug @@ -4,7 +4,7 @@ block head script(src='https://hcaptcha.com/1/api.js' async='' defer='defer') block nav-links - li.nav-item.active + li.nav-item a.nav-link(href='/') span Home li.nav-item @@ -19,24 +19,42 @@ block nav-links li.nav-item a.nav-link(href='https://blog.pastel.codes') span Blog - li.nav-item + li.nav-item.active a.nav-link(href='#') span Contact block content .container - form#contact-form(action='/contact' method='post' role='form') - .form-group - label(for='name') Name - input#name(name='name' class="form-control" type='text' placeholder='Your name' required='') - .form-group - label(for='email') Email - input#email(name='email' class="form-control" type='text' placeholder='Your email' required='') - .form-group - label(for='message') Message - textarea#message(name='message' class="form-control" placeholder='Enter your message here' rows='3' required='') - .h-captcha(data-sitekey='49abba50-1813-4ab3-acbf-2a8bfff1f7c3') - button(type='submit' class="btn btn-primary") Submit - if message - #contact-error - p=message + .row + .col + h1 + span Contact. + .row + .col + form#contact-form(action='/contact' method='post' role='form') + .form-group + label Name + .form-row + .col + input#fname(name='firstname' class="form-control" type='text' placeholder='First name' required="true" pattern="^[\\w'\\-,.][^0-9_!¡?÷?¿/\\\\+=@#$%ˆ&*(){}|~<>;:[\\]]{2,}$") + .col + input#lname(name='lastname' class="form-control" type='text' placeholder='Last name' required="true" pattern="^[\\w'\\-,.][^0-9_!¡?÷?¿/\\\\+=@#$%ˆ&*(){}|~<>;:[\\]]{2,}$") + + .form-group + label(for='email') Email + input#email(name='email' class="form-control" type='text' placeholder='Email (example@email.com)' required='true' pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$") + .form-group + label(for='message') Message + textarea#message(name='message' class="form-control" placeholder='Enter your message here' rows='3' required='true') + .form-group + .h-captcha(data-sitekey='49abba50-1813-4ab3-acbf-2a8bfff1f7c3') + button(type='submit' class="btn btn-primary") Submit + + .row + .col + if message + #contact-message + p=message + if success + p=success + span#email=email