Skip to content

Commit

Permalink
feat: sanity check the account SID and auth token when creating profi…
Browse files Browse the repository at this point in the history
…les (twilio#153)
  • Loading branch information
thinkingserious committed Feb 24, 2020
1 parent 5eccf45 commit 174fd53
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 32 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Before you submit your pull request consider the following guidelines:
* `cd ..`
* `git clone https://github.com/twilio/twilio-cli.git`
* `cd twilio-cli`
* In `package.json` replace `@twilio/cli-core": "<Version Number>"` with `@twilio/cli-core": "file:../twilio-cli-core`
* In `package.json` replace `"@twilio/cli-core": "<Version Number>"` with `"@twilio/cli-core": "file:../twilio-cli-core"`
* `make clean install`
* Test that everything is wired up correctuly with `./bin/run`

Expand Down
63 changes: 51 additions & 12 deletions src/commands/profiles/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const FRIENDLY_STORAGE_LOCATIONS = {
[STORAGE_LOCATIONS.LIBSECRET]: 'using libsecret'
};

const SKIP_VALIDATION = 'skip-parameter-validation';

class ProfilesCreate extends BaseCommand {
constructor(argv, config, secureStorage) {
super(argv, config, secureStorage);
Expand All @@ -38,8 +40,8 @@ class ProfilesCreate extends BaseCommand {
this.cancel();
}

this.validateAccountSid();
this.validateAuthToken();
this.loadAccountSid();
this.loadAuthToken();
await this.promptForCredentials();

if (await this.validateCredentials()) {
Expand All @@ -51,8 +53,6 @@ class ProfilesCreate extends BaseCommand {
}

loadArguments() {
this.accountSid = this.args['account-sid'];
this.authToken = this.flags['auth-token'];
this.profileId = this.flags.profile;
this.force = this.flags.force;
this.region = this.flags.region;
Expand All @@ -71,39 +71,74 @@ class ProfilesCreate extends BaseCommand {
}
}

validateAccountSid() {
loadAccountSid() {
this.accountSid = this.args['account-sid'];
if (!this.accountSid) {
this.questions.push({
name: 'accountSid',
message: this.getPromptMessage(ProfilesCreate.args[0].description),
validate: input => Boolean(input)
validate: input => this.validAccountSid(input)
});
}
}

validateAuthToken() {
loadAuthToken() {
this.authToken = this.flags['auth-token'];
if (!this.authToken) {
this.questions.push({
type: 'password',
name: 'authToken',
message: this.getPromptMessage(ProfilesCreate.flags['auth-token'].description),
validate: input => Boolean(input)
validate: input => this.validAuthToken(input)
});
}
}

validAccountSid(input) {
if (!input) {
return false;
}

if (!this.flags[SKIP_VALIDATION]) {
if (!input.startsWith('AC') || input.length !== 34) {
return 'Account SID must be "AC" followed by 32 hexadecimal digits (0-9, a-z)';
}
}

return true;
}

validAuthToken(input) {
if (!input) {
return false;
}

if (!this.flags[SKIP_VALIDATION]) {
if (input.length !== 32) {
return 'Auth Token must be 32 characters in length';
}
}

return true;
}

async promptForCredentials() {
if (this.questions) {
if (this.questions && this.questions.length > 0) {
this.logger.info(helpMessages.WHERE_TO_FIND_ACCOUNT_SID);
this.logger.error(helpMessages.AUTH_TOKEN_NOT_SAVED);
const answers = await this.inquirer.prompt(this.questions);
this.accountSid = answers.accountSid || this.accountSid;
this.authToken = answers.authToken || this.authToken;
}

if (!this.accountSid.toUpperCase().startsWith('AC')) {
throw new TwilioCliError('Account SID must be "AC" followed by 32 hexadecimal digits (0-9, a-z)');
}
const throwIfInvalid = valid => {
if (valid !== true) {
throw new TwilioCliError(valid || 'You must provide a valid value');
}
};

throwIfInvalid(this.validAccountSid(this.accountSid));
throwIfInvalid(this.validAuthToken(this.authToken));
}

async confirmOverwrite() {
Expand Down Expand Up @@ -215,6 +250,10 @@ ProfilesCreate.flags = Object.assign(
char: 'f',
description: 'Force overwriting existing profile credentials.'
}),
[SKIP_VALIDATION]: flags.boolean({
default: false,
hidden: true
}),
region: flags.string({
hidden: true
})
Expand Down
61 changes: 42 additions & 19 deletions test/commands/profiles/create.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,24 @@ describe('commands', () => {
.onThirdCall()
.resolves({
accountSid: constants.FAKE_ACCOUNT_SID,
authToken: constants.FAKE_API_SECRET
authToken: '0'.repeat(32)
});
ctx.testCmd.inquirer.prompt = fakePrompt;
ctx.testCmd.secureStorage.loadKeytar = sinon.fake.resolves(true);
});

const mockSuccess = api => {
api.get(`/2010-04-01/Accounts/${constants.FAKE_ACCOUNT_SID}.json`).reply(200, {
sid: constants.FAKE_ACCOUNT_SID
});
api.post(`/2010-04-01/Accounts/${constants.FAKE_ACCOUNT_SID}/Keys.json`).reply(200, {
sid: constants.FAKE_API_KEY,
secret: constants.FAKE_API_SECRET
});
};

createTest()
.nock('https://api.twilio.com', api => {
api.get(`/2010-04-01/Accounts/${constants.FAKE_ACCOUNT_SID}.json`).reply(200, {
sid: constants.FAKE_ACCOUNT_SID
});
api.post(`/2010-04-01/Accounts/${constants.FAKE_ACCOUNT_SID}/Keys.json`).reply(200, {
sid: constants.FAKE_API_KEY,
secret: constants.FAKE_API_SECRET
});
})
.nock('https://api.twilio.com', mockSuccess)
.do(ctx => ctx.testCmd.run())
.it('runs profiles:create', ctx => {
expect(ctx.stdout).to.equal('');
Expand Down Expand Up @@ -85,6 +87,35 @@ describe('commands', () => {
.catch(/Account SID must be "AC"/)
.it('fails for invalid account SIDs');

createTest()
.do(ctx => {
const fakePrompt = ctx.testCmd.inquirer.prompt;
fakePrompt.onThirdCall().resolves({
accountSid: constants.FAKE_ACCOUNT_SID,
authToken: '0'
});

return ctx.testCmd.run();
})
.catch(/Auth Token must be 32/)
.it('fails for invalid Auth Tokens');

createTest(['--skip-parameter-validation'])
.nock('https://api.twilio.com', mockSuccess)
.do(ctx => {
const fakePrompt = ctx.testCmd.inquirer.prompt;
fakePrompt.onThirdCall().resolves({
accountSid: constants.FAKE_ACCOUNT_SID,
authToken: 'blurgh'
});

return ctx.testCmd.run();
})
.it('can skip parameter validation', ctx => {
expect(ctx.stdout).to.equal('');
expect(ctx.stderr).to.contain('configuration saved');
});

createTest()
.do(ctx => {
process.env.TWILIO_ACCOUNT_SID = constants.FAKE_ACCOUNT_SID;
Expand Down Expand Up @@ -139,15 +170,7 @@ describe('commands', () => {
.it('fails early if keytar cannot be loaded');

createTest(['--region', 'dev'])
.nock('https://api.dev.twilio.com', api => {
api.get(`/2010-04-01/Accounts/${constants.FAKE_ACCOUNT_SID}.json`).reply(200, {
sid: constants.FAKE_ACCOUNT_SID
});
api.post(`/2010-04-01/Accounts/${constants.FAKE_ACCOUNT_SID}/Keys.json`).reply(200, {
sid: constants.FAKE_API_KEY,
secret: constants.FAKE_API_SECRET
});
})
.nock('https://api.dev.twilio.com', mockSuccess)
.do(async ctx => ctx.testCmd.run())
.it('supports other regions', ctx => {
expect(ctx.stdout).to.equal('');
Expand Down

0 comments on commit 174fd53

Please sign in to comment.