Skip to content

Commit

Permalink
Adds a config key to set the default login authType. Closes #5585
Browse files Browse the repository at this point in the history
  • Loading branch information
martinlingstuyl committed Oct 24, 2023
1 parent 129c6c7 commit 3e5be0d
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 10 deletions.
1 change: 1 addition & 0 deletions docs/docs/_clisettings.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Setting name|Definition|Default value
------------|----------|-------------
`authType`|Default login method to use when running `m365 login` without the `--authType` option.|`deviceCode`
`autoOpenLinksInBrowser`|Automatically open the browser for all commands which return a url and expect the user to copy paste this to the browser. For example when logging in, using `m365 login` in device code mode.|`false`
`copyDeviceCodeToClipboard`|Automatically copy the device code to the clipboard when running `m365 login` command in device code mode|`false`
`csvEscape`|Single character used for escaping; only apply to characters matching the quote and the escape options|`"`
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/cmd/login.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ m365 login [options]

Using the `login` command you can log in to Microsoft 365.

By default, the `login` command uses device code OAuth flow to log in to Microsoft 365. Alternatively, you can authenticate using a user name and password or certificate, which are convenient for CI/CD scenarios, but which come with their own [limitations](../user-guide/connecting-microsoft-365.mdx).
By default, the `login` command uses device code OAuth flow to log in to Microsoft 365. Alternatively, you can authenticate using a user name and password or certificate, which are convenient for CI/CD scenarios, but which come with their own [limitations](../user-guide/connecting-microsoft-365.mdx). The default `authType` can be configured using `m365 cli config set`. This means you'll be able to run `m365 login` without specifying the `--authType` option.

When logging in to Microsoft 365 using the user name and password, next to the access and refresh token, the CLI for Microsoft 365 will store the user credentials so that it can automatically re-authenticate if necessary. Similarly to the tokens, the credentials are removed by re-authenticating using the device code or by calling the [logout](logout.mdx) command.

Expand Down
2 changes: 1 addition & 1 deletion src/Auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ export class Auth {
const cli = Cli.getInstance();
cli.spinner.text = response.message;
cli.spinner.spinner = {
frames: ['🌶️']
frames: ['🌶️ ']
};

// don't show spinner if running tests
Expand Down
35 changes: 35 additions & 0 deletions src/m365/cli/commands/config/config-set.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,41 @@ describe(commands.CONFIG_SET, () => {
assert.strictEqual(actual, true);
});

it('fails validation if specified authType is invalid', async () => {
const actual = await command.validate({ options: { key: settingsNames.authType, value: 'invalid' } }, commandInfo);
assert.notStrictEqual(actual, true);
});

it('passes validation for authType type deviceCode', async () => {
const actual = await command.validate({ options: { key: settingsNames.authType, value: 'deviceCode' } }, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation for authType type browser', async () => {
const actual = await command.validate({ options: { key: settingsNames.authType, value: 'browser' } }, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation for authType type certificate', async () => {
const actual = await command.validate({ options: { key: settingsNames.authType, value: 'certificate' } }, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation for authType type password', async () => {
const actual = await command.validate({ options: { key: settingsNames.authType, value: 'password' } }, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation for authType type identity', async () => {
const actual = await command.validate({ options: { key: settingsNames.authType, value: 'identity' } }, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation for authType type secret', async () => {
const actual = await command.validate({ options: { key: settingsNames.authType, value: 'secret' } }, commandInfo);
assert.strictEqual(actual, true);
});

it('fails validation if specified error output type is invalid', async () => {
const actual = await command.validate({ options: { key: settingsNames.errorOutput, value: 'invalid' } }, commandInfo);
assert.notStrictEqual(actual, true);
Expand Down
6 changes: 6 additions & 0 deletions src/m365/cli/commands/config/config-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ class CliConfigSetCommand extends AnonymousCommand {
return `${args.options.value} is not a valid value for the option ${args.options.key}. Allowed values: ${Cli.helpModes.join(', ')}`;
}

const allowedAuthTypes = ['certificate', 'deviceCode', 'password', 'identity', 'browser', 'secret'];
if (args.options.key === settingsNames.authType &&
allowedAuthTypes.indexOf(args.options.value) === -1) {
return `${args.options.value} is not a valid value for the option ${args.options.key}. Allowed values: ${allowedAuthTypes.join(', ')}`;
}

return true;
}
);
Expand Down
21 changes: 13 additions & 8 deletions src/m365/commands/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import GlobalOptions from '../../GlobalOptions.js';
import { accessToken } from '../../utils/accessToken.js';
import { misc } from '../../utils/misc.js';
import commands from './commands.js';
import { settingsNames } from '../../settingsNames.js';
import { Cli } from '../../cli/Cli.js';

interface CommandArgs {
options: Options;
Expand Down Expand Up @@ -49,7 +51,7 @@ class LoginCommand extends Command {
#initTelemetry(): void {
this.telemetry.push((args: CommandArgs) => {
Object.assign(this.telemetryProperties, {
authType: args.options.authType || 'deviceCode',
authType: args.options.authType || Cli.getInstance().getSettingWithDefaultValue<string>(settingsNames.authType, 'deviceCode'),
cloud: args.options.cloud ?? CloudType.Public
});
});
Expand Down Expand Up @@ -95,7 +97,9 @@ class LoginCommand extends Command {
#initValidators(): void {
this.validators.push(
async (args: CommandArgs) => {
if (args.options.authType === 'password') {
const authType = args.options.authType || Cli.getInstance().getSettingWithDefaultValue<string>(settingsNames.authType, 'deviceCode');

if (authType === 'password') {
if (!args.options.userName) {
return 'Required option userName missing';
}
Expand All @@ -105,7 +109,7 @@ class LoginCommand extends Command {
}
}

if (args.options.authType === 'certificate') {
if (authType === 'certificate') {
if (args.options.certificateFile && args.options.certificateBase64Encoded) {
return 'Specify either certificateFile or certificateBase64Encoded, but not both.';
}
Expand All @@ -121,12 +125,12 @@ class LoginCommand extends Command {
}
}

if (args.options.authType &&
LoginCommand.allowedAuthTypes.indexOf(args.options.authType) < 0) {
return `'${args.options.authType}' is not a valid authentication type. Allowed authentication types are ${LoginCommand.allowedAuthTypes.join(', ')}`;
if (authType &&
LoginCommand.allowedAuthTypes.indexOf(authType) < 0) {
return `'${authType}' is not a valid authentication type. Allowed authentication types are ${LoginCommand.allowedAuthTypes.join(', ')}`;
}

if (args.options.authType === 'secret') {
if (authType === 'secret') {
if (!args.options.secret) {
return 'Required option secret missing';
}
Expand Down Expand Up @@ -155,10 +159,11 @@ class LoginCommand extends Command {
await logger.logToStderr(`Signing in to Microsoft 365...`);
}

const authType = args.options.authType || Cli.getInstance().getSettingWithDefaultValue<string>(settingsNames.authType, 'deviceCode');
auth.service.appId = args.options.appId || config.cliAadAppId;
auth.service.tenant = args.options.tenant || config.tenant;

switch (args.options.authType) {
switch (authType) {
case 'password':
auth.service.authType = AuthType.Password;
auth.service.userName = args.options.userName;
Expand Down
1 change: 1 addition & 0 deletions src/settingsNames.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const settingsNames = {
authType: 'authType',
autoOpenLinksInBrowser: 'autoOpenLinksInBrowser',
copyDeviceCodeToClipboard: 'copyDeviceCodeToClipboard',
csvEscape: 'csvEscape',
Expand Down

0 comments on commit 3e5be0d

Please sign in to comment.