Skip to content

Commit

Permalink
feat: add auth:device:login
Browse files Browse the repository at this point in the history
  • Loading branch information
mdonnalley committed Oct 13, 2020
1 parent dc231e5 commit 3e880d7
Show file tree
Hide file tree
Showing 13 changed files with 468 additions and 132 deletions.
11 changes: 11 additions & 0 deletions messages/device.login.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"description": "authorize an org using a device code",
"examples": [
"sfdx auth:device:login -d -a TestOrg1",
"sfdx auth:device:login -i <OAuth client id>",
"sfdx auth:device:login -r https://test.salesforce.com"
],
"actionRequired": "Action Required!",
"enterCode": "Enter %s user code in the verification URL %s",
"success": "Login successful for %s. You can now close the browser."
}
4 changes: 3 additions & 1 deletion messages/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,7 @@
"authorizeCommandSuccess": "Successfully authorized %s with org ID %s",
"authorizeCommandCloseBrowser": "You may now close the browser",
"warnAuth": "Logging in to a business or production org is not recommended on a demo or shared machine. Please run \"sfdx auth:logout --targetusername <your username> --noprompt\" when finished using this org, which is similar to logging out of the org in the browser.\n\nDo you want to authorize this org, %s, for use with the Salesforce CLI (y/n)?",
"noPromptAuth": "do not prompt for auth confirmation in demo mode"
"noPromptAuth": "do not prompt for auth confirmation in demo mode",
"disableMasking": "disable masking of user input (for use with problematic terminals)",
"clientSecretStdin": "OAuth client secret of personal connected app?"
}
2 changes: 0 additions & 2 deletions messages/web.login.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
"To log in to a sandbox, set --instanceurl to https://test.salesforce.com.",
"sfdx auth:web:login -r https://test.salesforce.com"
],
"disableMasking": "disable masking of user input (for use with problematic terminals)",
"deviceWarning": "auth:web:login doesn't work when authorizing to a headless environment. Use auth:device:login instead.",
"clientSecretStdin": "OAuth client secret of personal connected app?",
"invalidClientId": "Invalid client credentials. Verify the OAuth client secret and ID."
}
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"dependencies": {
"@oclif/config": "^1.17.0",
"@salesforce/command": "^3.0.3",
"@salesforce/core": "^2.12.1",
"@salesforce/core": "^2.13.0",
"@salesforce/kit": "^1.3.3",
"open": "^7.3.0",
"tslib": "^1"
Expand All @@ -17,7 +17,7 @@
"@salesforce/dev-config": "^2.0.0",
"@salesforce/dev-scripts": "^0.6.2",
"@salesforce/prettier-config": "^0.0.1",
"@salesforce/ts-sinon": "^1.2.2",
"@salesforce/ts-sinon": "^1.2.3",
"@types/mkdirp": "^1.0.1",
"@typescript-eslint/eslint-plugin": "^2.30.0",
"@typescript-eslint/parser": "^2.30.0",
Expand Down Expand Up @@ -84,6 +84,9 @@
},
"web": {
"description": "authorize an org using a web browser"
},
"device": {
"description": "authorize an org using a device code"
}
}
}
Expand Down
84 changes: 84 additions & 0 deletions src/commands/auth/device/login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import * as os from 'os';

import { flags, FlagsConfig, SfdxCommand } from '@salesforce/command';
import { AuthFields, DeviceOauthService, Messages, OAuth2Options } from '@salesforce/core';
import { Prompts } from '../../../prompts';
import { Common } from '../../../common';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/plugin-auth', 'device.login');
const commonMessages = Messages.loadMessages('@salesforce/plugin-auth', 'messages');

export default class Login extends SfdxCommand {
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessage('examples').split(os.EOL);
public static readonly flagsConfig: FlagsConfig = {
clientid: flags.string({
char: 'i',
description: commonMessages.getMessage('clientId'),
}),
instanceurl: flags.url({
char: 'r',
description: commonMessages.getMessage('instanceUrl'),
}),
setdefaultdevhubusername: flags.boolean({
char: 'd',
description: commonMessages.getMessage('setDefaultDevHub'),
}),
setdefaultusername: flags.boolean({
char: 's',
description: commonMessages.getMessage('setDefaultUsername'),
}),
setalias: flags.string({
char: 'a',
description: commonMessages.getMessage('setAlias'),
}),
disablemasking: flags.boolean({
description: commonMessages.getMessage('disableMasking'),
hidden: true,
}),
};

public async run(): Promise<AuthFields> {
if (await Prompts.shouldExitCommand(this.ux, this.flags.noprompt)) return {};

const oauthConfig: OAuth2Options = {
loginUrl: this.flags.instanceurl,
clientId: this.flags.clientid,
};

if (this.flags.clientid) {
oauthConfig.clientSecret = await Prompts.askForClientSecret(this.ux, this.flags.disablemasking);
}

const deviceOauthService = await DeviceOauthService.create(oauthConfig);
const loginData = await deviceOauthService.requestDeviceLogin();

if (this.flags.json) {
this.ux.logJson(loginData);
} else {
this.ux.styledHeader(messages.getMessage('actionRequired'));
this.ux.log(messages.getMessage('enterCode'), loginData.user_code, loginData.verification_uri);
this.ux.log();
}

const approval = await deviceOauthService.awaitDeviceApproval(loginData);
if (approval) {
const authInfo = await deviceOauthService.authorizeAndSave(approval);
await Common.handleSideEffects(authInfo, this.flags);
const fields = authInfo.getFields();
const successMsg = messages.getMessage('success', [fields.username]);
this.ux.log(successMsg);
return fields;
} else {
return {};
}
}
}
14 changes: 2 additions & 12 deletions src/commands/auth/jwt/grant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as os from 'os';
import { flags, FlagsConfig, SfdxCommand } from '@salesforce/command';
import { AuthFields, AuthInfo, AuthRemover, Messages, SfdxError } from '@salesforce/core';
import { Prompts } from '../../../prompts';
import { Common } from '../../../common';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/plugin-auth', 'jwt.grant');
Expand Down Expand Up @@ -64,18 +65,7 @@ export default class Grant extends SfdxCommand {

try {
const authInfo = await this.initAuthInfo();

if (this.flags.setalias) {
await authInfo.setAlias(this.flags.setalias);
}

if (this.flags.setdefaultdevhubusername || this.flags.setdefaultusername) {
await authInfo.setAsDefault({
defaultUsername: this.flags.setdefaultusername,
defaultDevhubUsername: this.flags.setdefaultdevhubusername,
});
}

await Common.handleSideEffects(authInfo, this.flags);
result = authInfo.getFields();
} catch (err) {
throw SfdxError.create('@salesforce/plugin-auth', 'jwt.grant', 'JwtGrantError', [err.message]);
Expand Down
15 changes: 3 additions & 12 deletions src/commands/auth/sfdxurl/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as os from 'os';
import { flags, FlagsConfig, SfdxCommand } from '@salesforce/command';
import { AuthFields, AuthInfo, fs, Messages } from '@salesforce/core';
import { Prompts } from '../../../prompts';
import { Common } from '../../../common';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/plugin-auth', 'sfdxurl.store');
Expand Down Expand Up @@ -54,19 +55,9 @@ export default class Store extends SfdxCommand {
const authInfo = await AuthInfo.create({ oauth2Options });
await authInfo.save();

let result: AuthFields = {};
if (this.flags.setalias) {
await authInfo.setAlias(this.flags.setalias);
}
await Common.handleSideEffects(authInfo, this.flags);

if (this.flags.setdefaultdevhubusername || this.flags.setdefaultusername) {
await authInfo.setAsDefault({
defaultUsername: this.flags.setdefaultusername,
defaultDevhubUsername: this.flags.setdefaultdevhubusername,
});
}

result = authInfo.getFields();
const result = authInfo.getFields();
const successMsg = commonMessages.getMessage('authorizeCommandSuccess', [result.username, result.orgId]);
this.ux.log(successMsg);
return result;
Expand Down
22 changes: 5 additions & 17 deletions src/commands/auth/web/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { flags, FlagsConfig, SfdxCommand } from '@salesforce/command';
import { AuthFields, AuthInfo, Messages, OAuth2Options, SfdxError, WebOAuthServer } from '@salesforce/core';
import { Env } from '@salesforce/kit';
import { Prompts } from '../../../prompts';
import { Common } from '../../../common';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/plugin-auth', 'web.login');
Expand Down Expand Up @@ -42,7 +43,7 @@ export default class Login extends SfdxCommand {
description: commonMessages.getMessage('setAlias'),
}),
disablemasking: flags.boolean({
description: messages.getMessage('disableMasking'),
description: commonMessages.getMessage('disableMasking'),
hidden: true,
}),
noprompt: flags.boolean({
Expand All @@ -66,25 +67,12 @@ export default class Login extends SfdxCommand {
};

if (this.flags.clientid) {
const secret = await this.ux.prompt(messages.getMessage('clientSecretStdin'), {
type: this.flags.disablemasking ? 'normal' : 'hide',
});
oauthConfig.clientSecret = secret;
oauthConfig.clientSecret = await Prompts.askForClientSecret(this.ux, this.flags.disablemasking);
}

try {
const authInfo = await this.executeLoginFlow(oauthConfig);

if (this.flags.setalias) {
await authInfo.setAlias(this.flags.setalias);
}

if (this.flags.setdefaultdevhubusername || this.flags.setdefaultusername) {
await authInfo.setAsDefault({
defaultUsername: this.flags.setdefaultusername,
defaultDevhubUsername: this.flags.setdefaultdevhubusername,
});
}
await Common.handleSideEffects(authInfo, this.flags);
const fields = authInfo.getFields();
const successMsg = commonMessages.getMessage('authorizeCommandSuccess', [fields.username, fields.orgId]);
this.ux.log(successMsg);
Expand All @@ -103,7 +91,7 @@ export default class Login extends SfdxCommand {
const oauthServer = await WebOAuthServer.create({ oauthConfig });
await oauthServer.start();
await open(oauthServer.getAuthorizationUrl(), { wait: false });
return oauthServer.loginAndSave();
return oauthServer.authorizeAndSave();
}

private isSFDXContainerMode(): boolean {
Expand Down
27 changes: 27 additions & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { AuthInfo } from '@salesforce/core';

interface Flags {
setalias?: string;
setdefaultdevhubusername?: boolean;
setdefaultusername?: boolean;
}

export class Common {
public static async handleSideEffects(authInfo: AuthInfo, flags: Flags): Promise<void> {
if (flags.setalias) await authInfo.setAlias(flags.setalias);

if (flags.setdefaultdevhubusername || flags.setdefaultusername) {
await authInfo.setAsDefault({
defaultUsername: flags.setdefaultusername,
defaultDevhubUsername: flags.setdefaultdevhubusername,
});
}
}
}
6 changes: 6 additions & 0 deletions src/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export class Prompts {
}
}

public static async askForClientSecret(ux: UX, disableMasking: false): Promise<string> {
return ux.prompt(messages.getMessage('clientSecretStdin'), {
type: disableMasking ? 'normal' : 'hide',
});
}

private static answeredYes(answer: string): boolean {
return ['YES', 'Y'].includes(answer.toUpperCase());
}
Expand Down
Loading

0 comments on commit 3e880d7

Please sign in to comment.