Skip to content

Commit

Permalink
feat: add new ask util command "generate-lwa-tokens"
Browse files Browse the repository at this point in the history
  • Loading branch information
RonWang committed Apr 15, 2020
1 parent 6998837 commit d74332f
Show file tree
Hide file tree
Showing 12 changed files with 317 additions and 45 deletions.
2 changes: 1 addition & 1 deletion bin/ask-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ utilCommander.commander.parse(process.argv);
if (!process.argv.slice(2).length) {
utilCommander.commander.outputHelp();
} else if (Object.keys(utilCommander.UTIL_COMMAND_MAP).indexOf(process.argv[2]) === -1) {
console.error('Command not recognized. Please run "askx util" to check the user instructions.');
console.error('Command not recognized. Please run "ask util" to check the user instructions.');
}
2 changes: 1 addition & 1 deletion lib/commands/configure/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class ConfigureCommand extends AbstractCommand {
}

optionalOptions() {
return ['noBrowser', 'profile', 'debug'];
return ['no-browser', 'profile', 'debug'];
}

handle(cmd, cb) {
Expand Down
20 changes: 19 additions & 1 deletion lib/commands/option-model.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"alias": null,
"stringInput": "NONE"
},
"noBrowser": {
"no-browser": {
"name": "no-browser",
"description": "ask cli displays a URL that you can use to sign in with your Amazon developer account from anywhere",
"alias": null,
Expand Down Expand Up @@ -100,5 +100,23 @@
"description": "Unique identifier of the catalog",
"alias": "c",
"stringInput": "REQUIRED"
},
"scopes": {
"name": "scopes",
"description": "request with particular scope(s) from Login with Amazon, delimited by space",
"alias": null,
"stringInput": "REQUIRED"
},
"client-confirmation": {
"name": "client-confirmation",
"description": "the client-secret when registering LWA application, uses CLI's default if not set",
"alias": null,
"stringInput": "REQUIRED"
},
"client-id": {
"name": "client-id",
"description": "the client-id when registering LWA application, uses CLI's default if not set",
"alias": null,
"stringInput": "REQUIRED"
}
}
72 changes: 72 additions & 0 deletions lib/commands/util/generate-lwa-tokens/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
const { AbstractCommand } = require('@src/commands/abstract-command');
const configureUi = require('@src/commands/configure/ui');
const optionModel = require('@src/commands/option-model');
const AuthorizationController = require('@src/controllers/authorization-controller');
const CONSTANTS = require('@src/utils/constants');
const jsonView = require('@src/view/json-view');
const Messenger = require('@src/view/messenger');

class GenerateLwaTokensCommand extends AbstractCommand {
name() {
return 'generate-lwa-tokens';
}

description() {
return 'generate Login with Amazon tokens from any LWA client';
}

requiredOptions() {
return [];
}

optionalOptions() {
return ['client-id', 'client-confirmation', 'scopes', 'no-browser', 'debug'];
}

handle(cmd, cb) {
const authConfig = {
auth_client_type: 'LWA',
clientId: cmd.clientId,
clientConfirmation: cmd.clientConfirmation,
scope: cmd.scopes,
doDebug: cmd.debug
}; // redirect_url must be pre-set depending on the CLI mode and with the trusted domain

if (cmd.browser === false) {
authConfig.redirectUri = CONSTANTS.LWA.S3_RESPONSE_PARSER_URL;
const lwaController = new AuthorizationController(authConfig);
const authorizeUrl = lwaController.getAuthorizeUrl();
Messenger.getInstance().info(`Paste the following url to your browser:\n ${authorizeUrl}`);
configureUi.getAuthCode((uiErr, authCode) => {
if (uiErr) {
Messenger.getInstance().error(uiErr);
return cb(uiErr);
}
lwaController.getAccessTokenUsingAuthCode(authCode, (getTokenErr, accessToken) => {
if (getTokenErr) {
Messenger.getInstance().error(getTokenErr);
return cb(getTokenErr);
}
Messenger.getInstance().info('\nThe LWA tokens result:');
Messenger.getInstance().info(jsonView.toString(accessToken));
return cb();
});
});
} else {
authConfig.redirectUri = `http://127.0.0.1:${CONSTANTS.LWA.LOCAL_PORT}/cb`;
const lwaController = new AuthorizationController(authConfig);
lwaController.getTokensByListeningOnPort((browserGetTokenErr, accessToken) => {
if (browserGetTokenErr) {
Messenger.getInstance().error(browserGetTokenErr);
return cb(browserGetTokenErr);
}
Messenger.getInstance().info('The LWA tokens result:');
Messenger.getInstance().info(jsonView.toString(accessToken));
cb();
});
}
}
}

module.exports = GenerateLwaTokensCommand;
module.exports.createCommand = new GenerateLwaTokensCommand(optionModel).createCommand();
16 changes: 9 additions & 7 deletions lib/commands/util/git-credentials-helper/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,23 @@ class GitCredentialsHelperCommand extends AbstractCommand {
return ['profile', 'debug'];
}

handle(cmd) {
handle(cmd, cb) {
let profile;
try {
profile = profileHelper.runtimeProfile(cmd.profile);
new ResourcesConfig(path.join(process.cwd(), CONSTANTS.FILE_PATH.ASK_RESOURCES_JSON_CONFIG));
} catch (err) {
Messenger.getInstance().error(err);
return;
return cb(err);
}

const skillId = ResourcesConfig.getInstance().getSkillId(profile);
const { repository } = ResourcesConfig.getInstance().getSkillInfraDeployState(profile);
if (!repository) {
Messenger.getInstance().error('Failed to get the git repository from ask-cli project. '
+ 'Please verify the completeness of your skill project.');
return;
const REPOSITORY_ERR_MSG = 'Failed to get the git repository from ask-cli project. '
+ 'Please verify the completeness of your skill project.';
Messenger.getInstance().error(REPOSITORY_ERR_MSG);
return cb(REPOSITORY_ERR_MSG);
}

const smapiClient = new SmapiClient({
Expand All @@ -53,17 +54,18 @@ class GitCredentialsHelperCommand extends AbstractCommand {
smapiClient.skill.alexaHosted.getGitCredentials(skillId, repository.url, (err, response) => {
if (err) {
Messenger.getInstance().error(err);
return;
return cb(err);
}
if (response.statusCode >= 300) {
const error = jsonView.toString(response.body);
Messenger.getInstance().error(error);
return;
return cb(error);
}
const { repositoryCredentials } = response.body;
const output = `username=${repositoryCredentials.username}
password=${repositoryCredentials.password}`;
Messenger.getInstance().info(output);
cb();
});
}
}
Expand Down
3 changes: 2 additions & 1 deletion lib/commands/util/util-commander.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ const commander = require('commander');

const UTIL_COMMAND_MAP = {
'upgrade-project': '@src/commands/util/upgrade-project',
'git-credentials-helper': '@src/commands/util/git-credentials-helper'
'git-credentials-helper': '@src/commands/util/git-credentials-helper',
'generate-lwa-tokens': '@src/commands/util/generate-lwa-tokens'
};

Object.keys(UTIL_COMMAND_MAP).forEach((cmd) => {
Expand Down
2 changes: 1 addition & 1 deletion lib/controllers/authorization-controller/messages.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports.AUTH_MESSAGE = 'Switch to "Login with Amazon" page and sign-in with your Amazon developer credentials.\n'
+ 'If your browser did not open the page, run the configuration process again with command "ask configure --no-browser".\n';
+ 'If your browser did not open the page, try to run the command again with "--no-browser" option.\n';

module.exports.PORT_OCCUPIED_WARN_MESSAGE = '[Warn]: 9090 port on localhost has been occupied, '
+ 'ask-cli cannot start a local server for receiving authorization code.\nPlease either abort any processes running on port 9090\n'
Expand Down
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,7 @@
"lib/commands/*"
],
"exclude": [
"lib/clients/aws-client/aws-util.js",
"lib/commands/api/account-linking/set-account-linking/*",
"lib/commands/api/catalog/upload-catalog/*"
"lib/clients/aws-client/aws-util.js"
]
},
"_moduleAliases": {
Expand Down
2 changes: 1 addition & 1 deletion test/unit/commands/configure/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('Commands Configure test - command class test', () => {
expect(instance.name()).eq('configure');
expect(instance.description()).eq('helps to configure the credentials that ask-cli uses to authenticate the user to Amazon developer services');
expect(instance.requiredOptions()).deep.eq([]);
expect(instance.optionalOptions()).deep.eq(['noBrowser', 'profile', 'debug']);
expect(instance.optionalOptions()).deep.eq(['no-browser', 'profile', 'debug']);
});

describe('validate command handle - ensure AppConfig initiated', () => {
Expand Down
171 changes: 171 additions & 0 deletions test/unit/commands/util/generate-lwa-tokens/index-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
const { expect } = require('chai');
const sinon = require('sinon');

const AuthorizationController = require('@src/controllers/authorization-controller');
const configureUi = require('@src/commands/configure/ui');
const GenerateLwaTokensCommand = require('@src/commands/util/generate-lwa-tokens');
const jsonView = require('@src/view/json-view');
const Messenger = require('@src/view/messenger');
const optionModel = require('@src/commands/option-model');

describe('Commands generate-lwa-tokens test - command class test', () => {
const TEST_DEBUG = false;

let infoStub;
let errorStub;
let warnStub;

beforeEach(() => {
infoStub = sinon.stub();
errorStub = sinon.stub();
warnStub = sinon.stub();
sinon.stub(Messenger, 'getInstance').returns({
info: infoStub,
error: errorStub,
warn: warnStub
});
});

afterEach(() => {
sinon.restore();
});

it('| validate command information is set correctly', () => {
const instance = new GenerateLwaTokensCommand(optionModel);
expect(instance.name()).equal('generate-lwa-tokens');
expect(instance.description()).equal('generate Login with Amazon tokens from any LWA client');
expect(instance.requiredOptions()).deep.equal([]);
expect(instance.optionalOptions()).deep.equal(['client-id', 'client-confirmation', 'scopes', 'no-browser', 'debug']);
});

describe('validate command handle', () => {
const TEST_CLIENT_ID = 'client id';
const TEST_CLIENT_CONFIRMATION = 'client confirmation';
const TEST_SCOPES = 'scopes1 scopes2';
const TEST_ERROR = 'error';
const TEST_AUTHORIZE_URL = 'authorize url';
const TEST_AUTH_CODE = 'auth code';
const TEST_ACCESS_TOKEN = {
access_token: 'AToken'
};
let instance;

beforeEach(() => {
sinon.stub(configureUi, 'getAuthCode');
sinon.stub(AuthorizationController.prototype, 'getAuthorizeUrl');
sinon.stub(AuthorizationController.prototype, 'getAccessTokenUsingAuthCode');
sinon.stub(AuthorizationController.prototype, 'getTokensByListeningOnPort');
instance = new GenerateLwaTokensCommand(optionModel);
});

afterEach(() => {
sinon.restore();
});

describe('command handle - no browser approach', () => {
it('| ui get authCode fails with error, expect error displayed', (done) => {
// setup
const TEST_CMD = {
browser: false,
debug: TEST_DEBUG
};
AuthorizationController.prototype.getAuthorizeUrl.returns(TEST_AUTHORIZE_URL);
configureUi.getAuthCode.callsArgWith(0, TEST_ERROR);
// call
instance.handle(TEST_CMD, (err) => {
// verify
expect(err).equal(TEST_ERROR);
expect(errorStub.args[0][0]).equal(TEST_ERROR);
expect(infoStub.callCount).equal(1);
expect(warnStub.callCount).equal(0);
done();
});
});

it('| lwa controller fails to get accessToken with the input authCode, expect error displayed', (done) => {
// setup
const TEST_CMD = {
browser: false,
debug: TEST_DEBUG
};
AuthorizationController.prototype.getAuthorizeUrl.returns(TEST_AUTHORIZE_URL);
configureUi.getAuthCode.callsArgWith(0, null, TEST_AUTH_CODE);
AuthorizationController.prototype.getAccessTokenUsingAuthCode.callsArgWith(1, TEST_ERROR);
// call
instance.handle(TEST_CMD, (err) => {
// verify
expect(err).equal(TEST_ERROR);
expect(errorStub.args[0][0]).equal(TEST_ERROR);
expect(infoStub.callCount).equal(1);
expect(warnStub.callCount).equal(0);
done();
});
});

it('| no-browser flow succeeds, expect ui displays properly', (done) => {
// setup
const TEST_CMD = {
clientId: TEST_CLIENT_ID,
clientConfirmation: TEST_CLIENT_CONFIRMATION,
scopes: TEST_SCOPES,
browser: false,
debug: TEST_DEBUG
};
AuthorizationController.prototype.getAuthorizeUrl.returns(TEST_AUTHORIZE_URL);
configureUi.getAuthCode.callsArgWith(0, null, TEST_AUTH_CODE);
AuthorizationController.prototype.getAccessTokenUsingAuthCode.callsArgWith(1, null, TEST_ACCESS_TOKEN);
// call
instance.handle(TEST_CMD, (err) => {
// verify
expect(err).equal(undefined);
expect(infoStub.args[0][0]).equal(`Paste the following url to your browser:\n ${TEST_AUTHORIZE_URL}`);
expect(infoStub.args[1][0]).equal('\nThe LWA tokens result:');
expect(infoStub.args[2][0]).equal(jsonView.toString(TEST_ACCESS_TOKEN));
expect(errorStub.callCount).equal(0);
expect(warnStub.callCount).equal(0);
done();
});
});
});

describe('command handle - use browser approach', () => {
it('| lwa controller fails to get token by listening, expect error displayed', (done) => {
// setup
const TEST_CMD = {
debug: TEST_DEBUG
};
AuthorizationController.prototype.getTokensByListeningOnPort.callsArgWith(0, TEST_ERROR);
// call
instance.handle(TEST_CMD, (err) => {
// verify
expect(err).equal(TEST_ERROR);
expect(errorStub.args[0][0]).equal(TEST_ERROR);
expect(infoStub.callCount).equal(0);
expect(warnStub.callCount).equal(0);
done();
});
});

it('| flow with browser succeeds, expect ui displays token info properly', (done) => {
// setup
const TEST_CMD = {
clientId: TEST_CLIENT_ID,
clientConfirmation: TEST_CLIENT_CONFIRMATION,
scopes: TEST_SCOPES,
debug: TEST_DEBUG
};
AuthorizationController.prototype.getTokensByListeningOnPort.callsArgWith(0, null, TEST_ACCESS_TOKEN);
// call
instance.handle(TEST_CMD, (err) => {
// verify
expect(err).equal(undefined);
expect(infoStub.args[0][0]).equal('The LWA tokens result:');
expect(infoStub.args[1][0]).equal(jsonView.toString(TEST_ACCESS_TOKEN));
expect(errorStub.callCount).equal(0);
expect(warnStub.callCount).equal(0);
done();
});
});
});
});
});
Loading

0 comments on commit d74332f

Please sign in to comment.