├── .env.example ├── .gitignore ├── .nowignore ├── Dockerfile ├── LICENSE ├── docker-compose.yml ├── index.js ├── now.json ├── package.json ├── readme.md ├── routes └── route.js ├── script └── dockerCompose.sh └── views ├── _footer.ejs ├── _header.ejs └── verify.ejs /.env.example: -------------------------------------------------------------------------------- 1 | SENDGRID_API_KEY_STATIC_CONTACT_VALIDATION=dasdasdasdas #Add your sendgrid API key to send emails. It can found at app.sendgrid.com 2 | SECRET_STATIC_CONTACT_VALIDATION=testSecret #This can be anything 3 | REDIRECT_URL_STATIC_CONTACT_VALIDATION=https://knrt10.github.io/thankyou/ #URL to redirect after token is authenticated 4 | EMAIL_RECEIVER_STATIC_CONTACT_VALIDATION=Kautilya_Tripathi #Name of person who is receiving email i.e you yourself 5 | RECEIVER_EMAIL_STATIC_CONTACT_VALIDATION=your@email.com #Your email address 6 | HOSTED_URL_STATIC_CONTACT_VALIDATION=https://hostedSite.com #Your site hosted URL # do not put / at the end of URL 7 | PORT_STATIC_CONTACT_VALIDATION=3000 # default is 3000 or anything you want 8 | TESTING_ENABLED_STATIC_CONTACT_VALIDATION=true # if you want to test using curl or any other service 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | .env 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.nowignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine 2 | 3 | # Create app directory 4 | RUN mkdir -p /usr/src/ 5 | 6 | WORKDIR /usr/src/ 7 | 8 | # Install app dependencies 9 | COPY package.json /usr/src/ 10 | 11 | RUN npm install --quiet 12 | 13 | # Bundle app source 14 | COPY . /usr/src 15 | 16 | EXPOSE 3000 17 | CMD ["npm", "start"] 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kautilya Tripathi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | web: 4 | build: . 5 | command: npm start 6 | container_name: static-contact-validation 7 | volumes: 8 | - .:/usr/src/ 9 | - /usr/src/node_modules 10 | ports: 11 | - "3000:3000" 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | // Start === requiring express modules 3 | const express = require('express') 4 | const useragent = require('express-useragent'); 5 | const bodyParser = require('body-parser') 6 | const cors = require('cors') 7 | const path = require('path') 8 | const app = express() 9 | const port = process.env.PORT_STATIC_CONTACT_VALIDATION || 3000 10 | // === END 11 | 12 | // Setting views for Administration 13 | 14 | app.set('views', path.join(__dirname, 'views')) 15 | app.set('view engine', 'ejs') 16 | 17 | // Start === requiring files 18 | const route = require('./routes/route') 19 | // === END 20 | 21 | // Start === Setting modules to use 22 | app.use(cors()) 23 | app.use(useragent.express()) 24 | app.use(bodyParser.urlencoded({extended: true})) 25 | app.use(bodyParser.json()) 26 | // === END 27 | 28 | // Start === Getting router and setting them 29 | app.use('/verify', route) 30 | // === END 31 | 32 | // Start === setting PORT 33 | app.listen(port, () => { 34 | console.log('Sever running on port ' + port) 35 | }) 36 | // === END 37 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static-contact-validation", 3 | "version": 2, 4 | "alias": "static-contact-validation", 5 | "env": { 6 | "SENDGRID_API_KEY_STATIC_CONTACT_VALIDATION": "@sendgrid-api-key-static-contact-validation", 7 | "SECRET_STATIC_CONTACT_VALIDATION": "@secret-static-contact-validation", 8 | "REDIRECT_URL_STATIC_CONTACT_VALIDATION": "@redirect-url-static-contact-validation", 9 | "EMAIL_RECEIVER_STATIC_CONTACT_VALIDATION": "@email-receiver-static-contact-validation", 10 | "RECEIVER_EMAIL_STATIC_CONTACT_VALIDATION": "@receiver-email-static-contact-validation", 11 | "HOSTED_URL_STATIC_CONTACT_VALIDATION": "@hosted-url-static-contact-validation", 12 | "PORT_STATIC_CONTACT_VALIDATION": "@port-static-contact-validation", 13 | "TESTING_ENABLED_STATIC_CONTACT_VALIDATION": "@testing_enabled_static_contact_validation" 14 | }, 15 | "builds": [ 16 | { 17 | "src": "*.js", 18 | "use": "@now/node" 19 | } 20 | ], 21 | "routes": [ 22 | { 23 | "src": "/verify.*", 24 | "dest": "/index.js", 25 | "methods": [ 26 | "GET" 27 | ] 28 | }], 29 | "github": { 30 | "enabled": false 31 | }, 32 | "regions": [ 33 | "gru" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "email-verification-static", 3 | "version": "1.0.0", 4 | "description": "Send contact emails from your static sites with verification of user", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "nodemon index.js", 8 | "now-start": "npm start", 9 | "dockerStart": "cd script && chmod 777 dockerCompose.sh && ./dockerCompose.sh && cd ..", 10 | "dockerStop": "docker-compose down" 11 | }, 12 | "author": "tripathi.kautilya@gmail.com ", 13 | "license": "MIT", 14 | "dependencies": { 15 | "@sendgrid/mail": "^6.3.1", 16 | "body-parser": "^1.18.3", 17 | "cors": "^2.8.4", 18 | "disposable-email": "^0.2.3", 19 | "dotenv": "^6.0.0", 20 | "ejs": "^2.6.1", 21 | "express": "^4.16.3", 22 | "express-useragent": "^1.0.13", 23 | "jsonwebtoken": "^8.3.0", 24 | "query-string": "^6.1.0", 25 | "store": "^2.0.12" 26 | }, 27 | "devDependencies": { 28 | "nodemon": "^1.18.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 |

5 | 6 | ## What is its use? 7 | 8 | You can add a contact form in your static website like site hosted on Github and able to get response from user. 9 | 10 | ### Some Cool Features 11 | 12 | Sometimes people try to span your email and you get anonymous emails. To avoid this, I have created this so that one has to authenticate their emails before sending message to you. 13 | 14 | - Avoid Spam emails 15 | - Check for blacklisted mail servers 16 | - Can add as many **name** fields in your HTML form. 17 | - Get IP address of the sender 18 | - Avoid DDOS attacks 19 | 20 | ## Preview 21 | 22 | ![Preview](https://res.cloudinary.com/dsyvg5xwi/image/upload/v1531587414/out3_hpfi8i.gif) 23 | 24 | ## Initial Setup 25 | 26 | ```bash 27 | git clone git@github.com:knrt10/static-contact-validatedForm.git 28 | 29 | cd static-contact-validatedForm/ 30 | 31 | touch .env #create .env file 32 | 33 | copy keys of .env.example in your .env file and fill all the details 34 | 35 | ``` 36 | 37 | ## How to setup your form 38 | 39 | 1.) Follow [Initial Setup](#initial-setup) 40 | 41 | 2.) Host it on any domain you want, I would suggest [Zeit Now](https://zeit.co/docs/v2/introduction/) 42 | 43 | 3.) Add an `.env` file in your root folder for reference see [.env.example](https://github.com/knrt10/static-contact-validatedForm/blob/master/.env.example) 44 | 45 | 4.) Add this to form in your static site 46 | 47 | - **Paste your endpoint into your form** 48 | 49 | *Change your form's action attribute like this:* 50 | 51 | **Important** method should be **GET** 52 | 53 | ```html 54 |
55 | ``` 56 | 57 | - **Add name attribute to every field** 58 | 59 | *Inputs, textareas, selects, radios should have unique name attributes* 60 | 61 | ```html 62 | 63 | 64 | 65 | ``` 66 | **name** field in html form for **email** field is mandatory. Rest you can add as many as you want. 67 | 68 | - **All set, you're ready to collect submissions** :fire: Enjoy. 69 | 70 | ## Development 71 | 72 | First follow [Initial Setup](#initial-setup) then you can use develop using 2 methods 73 | 74 | #### Docker 75 | 76 | ```bash 77 | 78 | npm run dockerStart # To start the service 79 | 80 | npm run dockerStop # To stop the service 81 | ``` 82 | 83 | You can access your app at port specified in your `.env` file or **3000** 84 | 85 | #### Local Development 86 | 87 | ```bash 88 | 89 | #install dependencies 90 | npm install 91 | 92 | #start the service 93 | npm start 94 | 95 | ``` 96 | 97 | ## license 98 | 99 | MIT [@knrt10](https://github.com/knrt10) 100 | -------------------------------------------------------------------------------- /routes/route.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const sgMail = require('@sendgrid/mail') 4 | const jwt = require('jsonwebtoken') 5 | const queryString = require('query-string') 6 | const store = require('store') 7 | const disposable = require('disposable-email'); 8 | 9 | const testingEnabled = process.env.TESTING_ENABLED_STATIC_CONTACT_VALIDATION 10 | // setting SENDGRID_API_KEY_STATIC_CONTACT_VALIDATION 11 | sgMail.setApiKey(process.env.SENDGRID_API_KEY_STATIC_CONTACT_VALIDATION) 12 | 13 | // getting informaion when one send Email 14 | 15 | router.get('/', (req, res, next) => { 16 | 17 | const ip = req.headers['x-forwarded-for'] || 18 | req.connection.remoteAddress || 19 | req.socket.remoteAddress || 20 | req.connection.socket.remoteAddress 21 | 22 | // check for userAgent that it is curl and avoid sending emailSent 23 | if (!req.useragent.isDesktop && testingEnabled === "false") { 24 | return res.json("Unauthorized to make request like this. This software is for web use only") 25 | } 26 | 27 | // setting an empty object 28 | const query = {} 29 | // Getting ip 30 | query['ip'] = ip 31 | 32 | if (req.query === undefined || req.query === null || req.query === '') { 33 | return res.json('Sorry you have not aceess to this') 34 | } 35 | 36 | // Checking req.query for name, email and message 37 | const { name, email, message} = req.query 38 | if (!name || !email || !message) { 39 | return res.json("Don't be smart and enter all fields " + ip); 40 | } 41 | 42 | //saving all info in object 43 | for (key in req.query) { 44 | query[key] = req.query[key] 45 | } 46 | 47 | // Validate email address 48 | emailValidated = disposable.validate(email) 49 | if (!emailValidated) { 50 | return res.json("Cannot use this email address, please go back and enter valid email address"); 51 | } 52 | 53 | let token = jwt.sign({ data: query }, process.env.SECRET_STATIC_CONTACT_VALIDATION) 54 | let tokenEmail = jwt.sign({ data: token }, process.env.SECRET_STATIC_CONTACT_VALIDATION, { 55 | expiresIn: '1h' 56 | }) 57 | 58 | // appending token in our object 59 | query['token'] = tokenEmail 60 | 61 | let queryies = queryString.stringify(query) 62 | 63 | let mailOptions = { 64 | from: 'Verify@Email.com', // sender address 65 | to: query.email, // list of receivers 66 | subject: 'Verify Your Email', // Subject line 67 | html: ` 68 |
69 | Click to verify your email : Click to verify 70 |

71 | After clicking the link your message will be sent to ` + process.env.EMAIL_RECEIVER_STATIC_CONTACT_VALIDATION + `. 72 |

73 | Please verify in 1 hour before it expires. 74 |

75 | Thank you for your patience 76 |
77 | ` 78 | } 79 | 80 | const emailSent = store.get("emailSent") 81 | if (emailSent === undefined) { 82 | sgMail.send(mailOptions, function (err) { 83 | if (err) { 84 | console.error(`${err.message} with code ${err.code}`) 85 | return res.json("Cannot send email to this address"); 86 | } 87 | store.set("emailSent", { verified: "true" }) 88 | store.set("userVerification", { verified: "false" }) 89 | res.render('verify', { data: query }) 90 | }) 91 | } else { 92 | res.render('verify', { data: query }) 93 | } 94 | }) 95 | 96 | router.get('/emailVerify', (req, res, next) => { 97 | 98 | const arr = [] 99 | const data = req.query 100 | const tokenEmail = data.token 101 | const verifiedData = store.get("userVerification") 102 | if (verifiedData && verifiedData.verified === "false") { 103 | try { 104 | const decoded = jwt.verify(tokenEmail, process.env.SECRET_STATIC_CONTACT_VALIDATION) 105 | 106 | // deleting token from out object 107 | delete data.token 108 | 109 | for (let prop in data) { 110 | if (data.hasOwnProperty(prop)) { 111 | let innerObj = {} 112 | innerObj[prop] = data[prop]; 113 | arr.push(innerObj) 114 | } 115 | } 116 | 117 | if (decoded.data) { 118 | let mailOptions = { 119 | from: data.email, // sender address 120 | to: process.env.RECEIVER_EMAIL_STATIC_CONTACT_VALIDATION, // list of receivers 121 | subject: 'Contact Message from your site', // Subject line 122 | html: ` 123 |
124 | ${arr.map((item) => ` 125 |

`+ Object.keys(item) + `: ` + Object.values(item) + `

126 | `.trim()).join('')} 127 |
` 128 | } 129 | 130 | sgMail.send(mailOptions, function (err) { 131 | if (err) return next(err) 132 | store.clearAll() 133 | res.redirect(process.env.REDIRECT_URL_STATIC_CONTACT_VALIDATION) 134 | }) 135 | } else { 136 | res.json('Wrong Token. Please check again.') 137 | } 138 | } catch (err) { 139 | res.json('Sorry not authorized for you.') 140 | } 141 | } else { 142 | res.json('Email already verified') 143 | } 144 | }) 145 | 146 | module.exports = router 147 | -------------------------------------------------------------------------------- /script/dockerCompose.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd .. 4 | 5 | docker build -t knrt10/static-contact-validation -f Dockerfile . 6 | 7 | docker-compose up 8 | -------------------------------------------------------------------------------- /views/_footer.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /views/_header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Verify Email 14 | 15 | 16 | -------------------------------------------------------------------------------- /views/verify.ejs: -------------------------------------------------------------------------------- 1 | <% include _header%> 2 |
3 |
4 |
5 |

Please verify your email with link sent to your email

6 |
7 |
8 | 9 |
10 |
11 |
12 |
13 |
14 | <% include _footer%> 15 | --------------------------------------------------------------------------------