From 51349a0a8491f4e80c914ee48c2d09ad354a79b7 Mon Sep 17 00:00:00 2001 From: Leandro Nunes Date: Tue, 10 Apr 2018 00:21:29 -0300 Subject: [PATCH] feat: List last 5 pushed password --- .travis.yml | 1 - __tests__/index.js | 60 ++++++++++++++++++++++++++++++++++------------ cli.js | 10 ++++++-- lib/pwpush.js | 59 ++++++++++++++++++++++++++++++++++++++------- package-lock.json | 7 +++++- package.json | 7 +++--- 6 files changed, 114 insertions(+), 30 deletions(-) diff --git a/.travis.yml b/.travis.yml index ca9b4d4..4e46166 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ language: node_js node_js: - 'node' - '8' - - '6' cache: directories: - node_modules diff --git a/__tests__/index.js b/__tests__/index.js index 50aa0b1..bba27f7 100644 --- a/__tests__/index.js +++ b/__tests__/index.js @@ -8,11 +8,12 @@ const pwpush = require('../lib/pwpush') const DEFAULT_EXPIRE_DAYS = '7' const DEFAULT_EXPIRE_VIEWS = '5' +const DEFAULT_LAST_ITEMS = '5' const HOST = 'https://pwpush.com' const PERMALINK = `${HOST}/p` -axios.defaults.host = HOST; -axios.defaults.adapter = httpAdapter; +axios.defaults.host = HOST +axios.defaults.adapter = httpAdapter const objDefault = { password: 'MyVerySecretP@sswd2BeBroken', @@ -33,6 +34,9 @@ const mockRequest = (json) => { return api } +beforeEach(() => { + pwpush.clearHistory() +}) afterEach(() => { nock.cleanAll() }) @@ -45,7 +49,7 @@ test(`Should throw an Error with message "A password is required!" if password w }) }) .toThrowError(`A password is required!`) -}); +}) test(`Should throw an Error with message "The password is too weak!" if password don't pass the OWASP test`, () => { expect(() => { pwpush({ @@ -53,7 +57,7 @@ test(`Should throw an Error with message "The password is too weak!" if password }) }) .toThrowError(`The password is too weak!`) -}); +}) test(`Should throw an Error with message containing security issues about OWASP test requirements`, () => { expect(() => { pwpush({ @@ -61,7 +65,7 @@ test(`Should throw an Error with message containing security issues about OWASP }) }) .toThrowError(`The password must be at least 10 characters long`) -}); +}) test(`Should throw an Error with message containing a link to OWASP security guideline`, () => { expect(() => { pwpush({ @@ -69,14 +73,14 @@ test(`Should throw an Error with message containing a link to OWASP security gui }) }) .toThrowError(`https://bit.ly/owasp-secure-guideline`) -}); +}) test(`Should have ${objDefault.expire_days} as default value for --days flag`, () => { - expect(DEFAULT_EXPIRE_DAYS).toBe(objDefault.expire_days); -}); + expect(DEFAULT_EXPIRE_DAYS).toBe(objDefault.expire_days) +}) test(`Should have ${objDefault.expire_views} as default value for --views flag`, () => { - expect(DEFAULT_EXPIRE_VIEWS).toBe(objDefault.expire_views); -}); + expect(DEFAULT_EXPIRE_VIEWS).toBe(objDefault.expire_views) +}) test(`Should allow weak password if --allow-weak flag is set`, () => { expect(() => { pwpush(Object.assign({}, objDefault, { @@ -85,7 +89,7 @@ test(`Should allow weak password if --allow-weak flag is set`, () => { })) }) .not.toThrow() -}); +}) test(`Should call pwpush.com with default request parameters`, (done) => { mockRequest(responseValid) @@ -103,7 +107,7 @@ test(`Should call pwpush.com with default request parameters`, (done) => { expect(result).toEqual(expected) done() }) -}); +}) test(`Should call pwpush.com API with custom request parameters`, (done) => { mockRequest(responseValid) @@ -125,7 +129,7 @@ test(`Should call pwpush.com API with custom request parameters`, (done) => { expect(result).toEqual(expected) done() }) -}); +}) test(`Should return an text message with "${PERMALINK}/${responseValid.url_token}" when response is valid`, (done) => { mockRequest(responseValid) @@ -140,7 +144,7 @@ test(`Should return an text message with "${PERMALINK}/${responseValid.url_token expect(res.text).toContain(`${PERMALINK}/${responseValid.url_token}`) done() }) -}); +}) test(`Should reject the promise with Error containing message "Something gets wrong!!" when response is invalid`, (done) => { mockRequest(responseInvalid) @@ -149,4 +153,30 @@ test(`Should reject the promise with Error containing message "Something gets wr expect(err).toEqual(Error(`Something gets wrong!!`)) done() }) -}); +}) + +test(`Should clear the list of pushed passwords`, async () => { + pwpush.clearHistory() + + const result = pwpush.showHistory().split('') + const expected = 0 + expect(result).toHaveLength(expected) +}) +test(`Should show a list of pushed password urls when --list flag is set`, async () => { + const max = parseInt(DEFAULT_LAST_ITEMS, 10) + for (let i=1; i<=max; i++) { + mockRequest(responseValid) + await pwpush({password: objDefault.password}) + + expect(pwpush.showHistory().split('\n')).toHaveLength(i) + } +}) +test(`Should show a list with no more than ${DEFAULT_LAST_ITEMS} pushed password items`, async () => { + const max = parseInt(DEFAULT_LAST_ITEMS, 10) + for (let i=1; i<=max + 1; i++) { + mockRequest(responseValid) + await pwpush({password: objDefault.password}) + } + + expect(pwpush.showHistory().split('\n')).toHaveLength(max) +}) diff --git a/cli.js b/cli.js index 9330786..c13bd19 100755 --- a/cli.js +++ b/cli.js @@ -10,8 +10,9 @@ const showHelp = (txt = '\r') => { \r $ pwpush [parameters] [options] \rParameters - \r --days | -d Days until the password is deleted. Default is ${pwpush.DEFAULT_EXPIRE_DAYS} + \r --days | -d Days until the password is deleted. Default is ${pwpush.DEFAULT_EXPIRE_DAYS} \r --views | -v Number of visualizations until the password is deleted. Default is ${pwpush.DEFAULT_EXPIRE_VIEWS} + \r --list | -l List last ${pwpush.DEFAULT_LAST_ITEMS} pushed passwords. \rOptions \r --allow-weak Allow weak passwords to be used. @@ -26,10 +27,11 @@ const showHelp = (txt = '\r') => { } const cli = parseArgs(process.argv.slice(2), { - boolean: ['version', 'help', 'allow-weak'], + boolean: ['version', 'help', 'allow-weak', 'list'], alias: { d: 'days', v: 'views', + l: 'list', h: 'help', }, unknown: (value) => { @@ -46,6 +48,10 @@ if (!!cli.version) { if (!!cli.help || !cli._[0]) { showHelp() } +if (!!cli.list) { + console.log(pwpush.showHistory()) + process.exit(0) +} const spinner = ora().start() diff --git a/lib/pwpush.js b/lib/pwpush.js index e4a4e9f..1b865a4 100644 --- a/lib/pwpush.js +++ b/lib/pwpush.js @@ -1,44 +1,84 @@ const axios = require('axios') const clip = require('node-clipboard') const owasp = require('owasp-password-strength-test') +const storage = require('user-settings').file('.pwpushcli'); const querystring = require('querystring') const DEFAULT_EXPIRE_DAYS = '7' const DEFAULT_EXPIRE_VIEWS = '5' +const DEFAULT_LAST_ITEMS = '5' const HOST = 'https://pwpush.com' const PERMALINK = `${HOST}/p` -const onResponseComplete = (response) => { +const onApiResponseComplete = (response) => { const json = response.data if( !json.url_token || !json.expire_after_days || !json.expire_after_views) { throw new Error(`Something gets wrong!!`) } + const url = getUrlValue(json) + + clip(url) + setHistory(url, json) + return { _: response, - text: getResultText(json), + text: getResultText(url, json), } } -const getResultText = (json) => { - const url = getUrlValue(json) - clip(url) - +const getResultText = (url, json) => { return ` ${url} \r=> ${getExpirationDate(json)}` } - const getUrlValue = ({url_token}) => `${PERMALINK}/${url_token}` const getExpirationDate = ({expire_after_days, expire_after_views}) => ( `This secret link will be deleted in ${expire_after_days} day or ${expire_after_views} more views` ) +const clearHistory = () => { + storage.unset('history') +} +const showHistory = () => { + return getHistory() + .map((item) => `- [${formatDate(item.date)}] ${item.url} (days ${item.days}, views ${item.views})`) + .join('\n') +} +const getHistory = () => [] + .concat(storage.get('history')) + .filter(item => !!item) + +const setHistory = (url, {expire_after_days, expire_after_views}) => { + storage.set('history', [ + { + date: Date.now(), + url: url, + days: expire_after_days, + views: expire_after_views, + }] + .concat(getHistory()) + .slice(0, DEFAULT_LAST_ITEMS) + ) +} + +const formatDate = (date) => { + const d = new Date(date) + const strDate = `${d.toLocaleDateString(undefined, { + year: 'numeric', + month: '2-digit', + day: '2-digit' + })}` + + return strDate +} + module.exports = ({ password, expire_days = DEFAULT_EXPIRE_DAYS, expire_views = DEFAULT_EXPIRE_VIEWS, allow_weak = false, + showHistory }) => { if (!password) { throw new Error(`A password is required!`) @@ -68,5 +108,8 @@ module.exports = ({ } return axios(reqOptions) - .then(onResponseComplete) + .then(onApiResponseComplete) } + +module.exports.clearHistory = clearHistory +module.exports.showHistory = showHistory diff --git a/package-lock.json b/package-lock.json index 7de03bf..14771e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "pwpush-cli", - "version": "0.6.1", + "version": "0.7.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -6399,6 +6399,11 @@ } } }, + "user-settings": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/user-settings/-/user-settings-0.2.0.tgz", + "integrity": "sha512-y0nkqj93bYuOaYJWAM8FhYUDg67dC9iPLY6lPwTwguP/amuSTHKAc1QSvSw16jmrKVcPyV6ti2e682vqE0Rdvw==" + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index d5f2f2a..65d234f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pwpush-cli", "description": "A nodeJS CLI wrapper to easily push passwords to pwpush.com", - "version": "0.6.1", + "version": "0.7.0", "author": { "name": "Leandro Nunes", "email": "dr1design@gmail.com", @@ -33,7 +33,7 @@ }, "main": "cli.js", "engines": { - "node": ">=6" + "node": ">=8" }, "scripts": { "prepublish": "npm test", @@ -50,7 +50,8 @@ "minimist": "^1.2.0", "node-clipboard": "^1.2.0", "ora": "^2.0.0", - "owasp-password-strength-test": "^1.3.0" + "owasp-password-strength-test": "^1.3.0", + "user-settings": "^0.2.0" }, "devDependencies": { "coveralls": "^3.0.0",