Skip to content

Commit

Permalink
Merge pull request #59 from zapier/login-logout-commands
Browse files Browse the repository at this point in the history
feat(cli) port login command
  • Loading branch information
xavdid authored Aug 30, 2019
2 parents c04c81b + c05eb03 commit 38dcc79
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 173 deletions.
1 change: 0 additions & 1 deletion packages/cli/src/commands/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ module.exports = {
history: require('./history'),
invite: require('./invite'),
link: require('./link'),
login: require('./login'),
logout: require('./logout'),
logs: require('./logs'),
migrate: require('./migrate'),
Expand Down
108 changes: 0 additions & 108 deletions packages/cli/src/commands/login.js

This file was deleted.

56 changes: 28 additions & 28 deletions packages/cli/src/oclif/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ Some notes to help an ongoing project

## helpful links

* the docs are fairly useful: https://oclif.io/docs/commands
* base command source is great for seeing what's available: https://github.com/oclif/command/blob/master/src/command.ts
- the docs are fairly useful: https://oclif.io/docs/commands
- base command source is great for seeing what's available: https://github.com/oclif/command/blob/master/src/command.ts

## migrating a command

Expand All @@ -18,32 +18,32 @@ Some notes to help an ongoing project

### easy

* [ ] apps
* [ ] delete
* [ ] describe
* [ ] deprecate
* [ ] help
* [ ] history
* [ ] invitees
* [ ] link
* [ ] login
* [ ] logout
* [ ] logs
* [ ] migrate
* [ ] migrate
* [ ] register
* [ ] test
- [ ] apps
- [ ] delete
- [ ] describe
- [ ] deprecate
- [ ] help
- [ ] history
- [ ] invitees
- [ ] link
- [x] login
- [ ] logout
- [ ] logs
- [ ] migrate
- [ ] migrate
- [ ] register
- [ ] test

## hard

* [ ] build
* [ ] collaborate
* [ ] convert
* [ ] env
* [ ] invite
* [ ] promote
* [ ] push
* [ ] scaffold
* [ ] upload
* [ ] watch - remove?
* [ ] validate
- [ ] build
- [ ] collaborate
- [ ] convert
- [ ] env
- [ ] invite
- [ ] promote
- [ ] push
- [ ] scaffold
- [ ] upload
- [ ] watch - remove?
- [ ] validate
56 changes: 49 additions & 7 deletions packages/cli/src/oclif/ZapierBaseCommand.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const { Command } = require('@oclif/command');
const colors = require('colors/safe');

const { startSpinner, endSpinner, formatStyles } = require('../utils/display');
const { isValidAppInstall } = require('../utils/misc');

const inquirer = require('inquirer');

Expand All @@ -17,7 +19,23 @@ class ZapierBaseCommand extends Command {
this.debug('args are', this.args);
this.debug('flags are', this.flags);
this.debug('------------');
return this.perform();

this.throwForInvalidAppInstall();

return this.perform().catch(e => {
this.stopSpinner({ success: false });
const errTextLines = [e.message];

this.debug(e.stack);

if (!this.flags.debug) {
errTextLines.push(
colors.gray('re-run this command with `--debug` for more info')
);
}

this.error(errTextLines.join('\n\n'));
});
}

_parseFlags() {
Expand All @@ -31,6 +49,14 @@ class ZapierBaseCommand extends Command {
this.error(`subclass the "perform" method in the "${this.id}" command`);
}

// ; put ina method so we can disable it easily in tests
throwForInvalidAppInstall() {
const { valid, reason } = isValidAppInstall(this.id);
if (!valid) {
this.error(reason);
}
}

// UTILS
/**
* Helps us not have helpful UI messages when the whole output should only be JSON.
Expand Down Expand Up @@ -80,21 +106,37 @@ class ZapierBaseCommand extends Command {
}
}

async confirm(message, defaultAns = false) {
/**
* get user input
* @param {string} question the question to ask the user
* @param {object} opts `inquierer.js` opts ([read more](https://github.com/SBoudrias/Inquirer.js/#question))
*/
async prompt(question, opts = {}) {
const { ans } = await inquirer.prompt({
type: 'confirm',
message,
default: defaultAns,
name: 'ans'
type: 'string',
...opts,
name: 'ans',
message: question
});
return ans;
}

promptHidden(question) {
return this.prompt(question, {
type: 'password',
mask: true
});
}

confirm(message, defaultAns = false) {
return this.prompt(message, { default: defaultAns, type: 'confirm' });
}

/**
* should only print to stdout when in a non-data mode
*/
_shouldPrintData() {
return this.flags.format && !DATA_FORMATS.includes(this.flags.format);
return !this.flags.format || !DATA_FORMATS.includes(this.flags.format);
}

startSpinner(message) {
Expand Down
99 changes: 99 additions & 0 deletions packages/cli/src/oclif/commands/login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
const colors = require('colors/safe');

const BaseCommand = require('../ZapierBaseCommand');
const { buildFlags } = require('../buildFlags');

const {
AUTH_LOCATION,
AUTH_LOCATION_RAW,
AUTH_KEY
} = require('../../constants');
const {
readCredentials,
checkCredentials,
createCredentials
} = require('../../utils/api');
const { writeFile } = require('../../utils/files');
const { prettyJSONstringify } = require('../../utils/display');

const isValidTotpCode = i => {
const num = parseInt(i, 10);
return Number.isInteger(num) && i.length === 6
? true
: 'Must be a 6 digit number';
};

class LoginCommand extends BaseCommand {
async perform() {
const checks = [
readCredentials()
.then(() => true)
.catch(() => false),
checkCredentials()
.then(() => true)
.catch(() => false)
];
const [credentialsPresent, credentialsGood] = await Promise.all(checks);

if (!credentialsPresent) {
this.log(
colors.yellow(`Your ${AUTH_LOCATION} has not been set up yet.\n`)
);
} else if (!credentialsGood) {
this.log(
colors.red(
`Your ${AUTH_LOCATION} looks like it has invalid credentials.\n`
)
);
} else {
this.log(
colors.green(
`Your ${AUTH_LOCATION} looks valid. You may update it now though.\n`
)
);
}
const username = await this.prompt(
'What is your Zapier login email address? (Ctrl-C to cancel)'
);
this.log(
`\n\nNow you'll enter your Zapier password.\nIf you log into Zapier via the ${colors.green(
'log in with Google button'
)}, you may not have a Zapier password.\nIf that's the case, go to https://zapier.com/app/login/forgot and create one.\n\n`
);
const password = await this.promptHidden(
'What is your Zapier login password?'
);

let goodResponse;
try {
goodResponse = await createCredentials(username, password);
} catch ({ errText, json: { errors } }) {
if (errors[0].startsWith('missing totp_code')) {
const code = await this.prompt(
'What is your current 6-digit 2FA code?',
{ validate: isValidTotpCode }
);
goodResponse = await createCredentials(username, password, code);
} else {
this.error(errText);
}
}
const deployKey = goodResponse.key;
await writeFile(
AUTH_LOCATION,
prettyJSONstringify({
[AUTH_KEY]: deployKey
})
);

await checkCredentials();

this.log(`Your deploy key has been saved to ${AUTH_LOCATION}. `);
}
}

LoginCommand.flags = buildFlags();
LoginCommand.examples = ['zapier login'];
LoginCommand.description = `Configure your \`${AUTH_LOCATION_RAW}\` with a deploy key.`;

module.exports = LoginCommand;
1 change: 1 addition & 0 deletions packages/cli/src/oclif/oCommands.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@

module.exports = {
init: require('./commands/init'),
login: require('./commands/login'),
versions: require('./commands/versions')
};
Loading

0 comments on commit 38dcc79

Please sign in to comment.