diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..1539bfb --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,43 @@ +# This is a basic workflow to help you get started with Actions + +name: CI + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + push: + branches: [ master ] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + # Runs a single command using the runners shell + - name: Build and Publish Docker + uses: elgohr/Publish-Docker-Github-Action@master + with: + name: abhishek971/dumbass + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: executing remote ssh commands using password + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + key: ${{ secrets.PRIVATE_KEY }} + script: docker pull abhishek971/dumbass:latest && cd ~/dumbass/ && git pull && docker-compose up -d + +# # Runs a set of commands using the runners shell +# - name: Run a multi-line script +# run: | +# echo Add other actions to build, +# echo test, and deploy your project. diff --git a/.gitignore b/.gitignore index 8500911..8a306ea 100644 --- a/.gitignore +++ b/.gitignore @@ -105,4 +105,5 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode-test -.idea \ No newline at end of file +.idea +.docker_env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fd9e9ac --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM node:12-alpine + +WORKDIR /usr/src/dumbass + +COPY package.json . +RUN yarn + +COPY . . + +EXPOSE 5001 + +CMD ["yarn", "start"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b57fdb7 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Dumbass +![CI](https://github.com/abhishek97/dumbass/workflows/CI/badge.svg) + +A Micro OTP send and verify services written in Node and Mongodb + +### Development +Make sure you have mongodb installed. +``` +node src/index.js +``` + +### Deployment +Can be run using `docker-compose`. +``` +docker-compose up +``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6ba937d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3' +services: + dumbass: + image: abhishek971/dumbass:latest + ports: + - "5001:5001" + links: + - mongo + env_file: + - .docker_env + + mongo: + image: mongo + volumes: + - mongo_data:/data/db + ports: + - "27017:27017" + env_file: + - .docker_env + +volumes: + mongo_data: \ No newline at end of file diff --git a/package.json b/package.json index aba262a..402ec96 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "main": "src/index.js", "author": "abhishek97", "license": "MIT", + "scripts": { + "start": "node src/index.js" + }, "dependencies": { "@hapi/joi": "^17.1.0", "@sendgrid/mail": "^6.5.2", @@ -12,6 +15,11 @@ "dotenv": "^8.2.0", "express": "^4.17.1", "mongodb": "^3.5.3", - "mustache": "^4.0.0" + "mustache": "^4.0.0", + "passport": "^0.4.1", + "passport-http-bearer": "^1.0.1" + }, + "devDependencies": { + "morgan": "^1.9.1" } } diff --git a/src/index.js b/src/index.js index d998658..2f08de8 100644 --- a/src/index.js +++ b/src/index.js @@ -1,16 +1,21 @@ -require('dotenv').config() +if (process.env.NODE_ENV !== 'production') + require('dotenv').config() + const express = require('express') +const morgan = require('morgan') const app = express() const Sentry = require('@sentry/node'); const { dbConnectionReady } = require('services/db') const routes = require('./routes') -const PORT = process.env.PORT || 5001 +const PORT = process.env.PORT || 5002 Sentry.init({ dsn: process.env.SENTRY_DSN }) app.use(express.json()) +app.use(morgan('dev')) +app.use((req, res, next) => { console.log(req.body); next() }) app.use(routes) dbConnectionReady.then(() => { diff --git a/src/passport/index.js b/src/passport/index.js new file mode 100644 index 0000000..c7d4003 --- /dev/null +++ b/src/passport/index.js @@ -0,0 +1,16 @@ +const passport = require('passport'); +const BearerStatergy = require('passport-http-bearer'); + +const {getClientByToken} = require('services/db'); + + +passport.use(new BearerStatergy( + async (token,done) => { + const client = await getClientByToken(token) + if(!client) + return done(null,false) + return done(null,client) + } +)); + +module.exports = passport; \ No newline at end of file diff --git a/src/routes/otp/controller.js b/src/routes/otp/controller.js index 843b295..e108381 100644 --- a/src/routes/otp/controller.js +++ b/src/routes/otp/controller.js @@ -17,7 +17,8 @@ module.exports.handleSendOtp = async (req, res, next) => { // create otp in mongo const { id: createdOtpId, revert } = await createOtp({ type: 'mobile', - mobile: dialCode + mobile, + mobile, + dialCode, message: messageText, otp, payload @@ -31,7 +32,7 @@ module.exports.handleSendOtp = async (req, res, next) => { } catch (err) { sentry.captureException(err) await revert() - return res.json(new ResponseError('SMS_API_ERROR', "Can't send OTP to that number")) + return res.status(400).json(new ResponseError('SMS_API_ERROR', "Can't send OTP to that number")) } } else if (req.body.email) { @@ -58,7 +59,7 @@ module.exports.handleSendOtp = async (req, res, next) => { sentry.captureException(err) // revert DB insert await revert() - return res.json(new ResponseError('EMAIL_API_ERROR', "Can't send OTP to that email")) + return res.status(400).json(new ResponseError('EMAIL_API_ERROR', "Can't send OTP to that email")) } } else { @@ -68,6 +69,11 @@ module.exports.handleSendOtp = async (req, res, next) => { module.exports.handleVerifyOtp = async (req, res, next) => { const { code } = req.body + + if (req.params.id.length != 24) { + return res.status(404).json(new ResponseError('OTP_NOT_FOUND')) + } + const otp = await getOtpById(req.params.id) if (!otp) { @@ -87,7 +93,7 @@ module.exports.handleVerifyOtp = async (req, res, next) => { } if (otp.verifiedAt) - return res.status(400).json(new ResponseError('ALREADY_VERIFIED', 'the otp is already consumed')) + return res.status(400).json(new ResponseError('ALREADY_VERIFIED', 'the otp is already verified')) // otp is valid and we update the claim await updateOtpById(otp._id, { diff --git a/src/routes/otp/index.js b/src/routes/otp/index.js index 006f287..fef0301 100644 --- a/src/routes/otp/index.js +++ b/src/routes/otp/index.js @@ -1,15 +1,18 @@ const { Router } = require('express') const validators = require('./validator') const controller = require('./controller') +const passport = require('../../passport') const route = Router() route.get('/', validators.POST, (req, res) => { res.send('OK. Get this') }) +route.use(passport.initialize()) +route.use(passport.authenticate('bearer', { session: false })) +route.post('/send', validators.POST, controller.handleSendOtp) route.get('/:id', controller.handleGetById) route.post('/:id/verify', controller.handleVerifyOtp) -route.post('/', validators.POST, controller.handleSendOtp) route.delete('/:id', controller.handleDeleteById) module.exports = route diff --git a/src/services/db.js b/src/services/db.js index dda98da..04c407b 100644 --- a/src/services/db.js +++ b/src/services/db.js @@ -25,4 +25,8 @@ module.exports.getOtpById = (id) => db.collection('otps').findOne({ module.exports.updateOtpById = (id, payload) => db.collection('otps').updateOne({ _id: new ObjectId(id) -}, { $set: payload }) \ No newline at end of file +}, { $set: payload }) + +module.exports.getClientByToken = async(clientToken) => db.collection('clients').findOne({ + token : clientToken.toString() + }) \ No newline at end of file diff --git a/src/services/sms.js b/src/services/sms.js index ca21b6a..7e04eea 100644 --- a/src/services/sms.js +++ b/src/services/sms.js @@ -1,13 +1,18 @@ const axios = require('axios') -module.exports.sendSms = (mobile, messageText) => { - return axios.get('http://sms.smscollection.com/sendsmsv2.asp',{ +module.exports.sendSms = async(mobile, messageText) => { + const response = await axios.get('http://transactional.msgadvert.com/http-api.php',{ params: { - user: process.env.MOBILE_VERIFY_USERNAME, + username: process.env.MOBILE_VERIFY_USERNAME, password: process.env.MOBILE_VERIFY_PASS, - sender: 'CDGBLK', - text: messageText, - PhoneNumber: mobile.replace("+", "").replace("-", "") + senderid: 'CDGBLK', + route: 2, + message: messageText, + number: mobile.replace("+", "").replace("-", "") } }) + // if(response.data.includes("Message Submitted")) + return response + + // throw new Error(response.data) } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 784f45f..29218e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -240,6 +240,13 @@ axios@^0.19.2: dependencies: follow-redirects "1.5.10" +basic-auth@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" + integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== + dependencies: + safe-buffer "5.1.2" + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -737,6 +744,17 @@ mongodb@^3.5.3: optionalDependencies: saslprep "^1.0.0" +morgan@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.1.tgz#0a8d16734a1d9afbc824b99df87e738e58e2da59" + integrity sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA== + dependencies: + basic-auth "~2.0.0" + debug "2.6.9" + depd "~1.1.2" + on-finished "~2.3.0" + on-headers "~1.0.1" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -774,16 +792,46 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" +on-headers@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +passport-http-bearer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/passport-http-bearer/-/passport-http-bearer-1.0.1.tgz#147469ea3669e2a84c6167ef99dbb77e1f0098a8" + integrity sha1-FHRp6jZp4qhMYWfvmdu3fh8AmKg= + dependencies: + passport-strategy "1.x.x" + +passport-strategy@1.x.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= + +passport@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.1.tgz#941446a21cb92fc688d97a0861c38ce9f738f270" + integrity sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg== + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +pause@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" + integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10= + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"