Skip to content

Commit

Permalink
feat: hide secrets in yarn config commands (#1228)
Browse files Browse the repository at this point in the history
* feat: hide secrets in `yarn config` commands

* chore(release-workflow): set releases

* refactor: change `getSecret` to `getForDisplay`

* test: add test for hiding secrets
  • Loading branch information
paul-soporan authored Apr 22, 2020
1 parent b22a72c commit 4daea78
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 29 deletions.
19 changes: 10 additions & 9 deletions .pnp.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions .yarn/versions/54448882.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
releases:
"@yarnpkg/plugin-essentials": prerelease
"@yarnpkg/cli": prerelease
"@yarnpkg/core": prerelease

declined:
- "@yarnpkg/plugin-compat"
- "@yarnpkg/plugin-constraints"
- "@yarnpkg/plugin-dlx"
- "@yarnpkg/plugin-exec"
- "@yarnpkg/plugin-file"
- "@yarnpkg/plugin-git"
- "@yarnpkg/plugin-github"
- "@yarnpkg/plugin-http"
- "@yarnpkg/plugin-init"
- "@yarnpkg/plugin-interactive-tools"
- "@yarnpkg/plugin-link"
- "@yarnpkg/plugin-node-modules"
- "@yarnpkg/plugin-npm"
- "@yarnpkg/plugin-npm-cli"
- "@yarnpkg/plugin-pack"
- "@yarnpkg/plugin-patch"
- "@yarnpkg/plugin-pnp"
- "@yarnpkg/plugin-stage"
- "@yarnpkg/plugin-typescript"
- "@yarnpkg/plugin-version"
- "@yarnpkg/plugin-workspace-tools"
- "@yarnpkg/builder"
- "@yarnpkg/doctor"
- "@yarnpkg/pnpify"
27 changes: 8 additions & 19 deletions packages/plugin-essentials/sources/commands/config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {BaseCommand} from '@yarnpkg/cli';
import {Configuration, MessageName, SettingsType, StreamReport} from '@yarnpkg/core';
import {miscUtils} from '@yarnpkg/core';
import {Command, Usage} from 'clipanion';
import {inspect} from 'util';
import {BaseCommand} from '@yarnpkg/cli';
import {Configuration, MessageName, StreamReport} from '@yarnpkg/core';
import {miscUtils} from '@yarnpkg/core';
import {Command, Usage} from 'clipanion';
import {inspect} from 'util';

// eslint-disable-next-line arca/no-default-export
export default class ConfigCommand extends BaseCommand {
Expand Down Expand Up @@ -40,17 +40,6 @@ export default class ConfigCommand extends BaseCommand {
strict: false,
});

const getValue = (key: string) => {
const isSecret = configuration.settings.get(key)!.type === SettingsType.SECRET;
const rawValue = configuration.values.get(key)!;

if (isSecret && typeof rawValue === `string`) {
return `********`;
} else {
return rawValue;
}
};

const report = await StreamReport.start({
configuration,
json: this.json,
Expand All @@ -69,7 +58,7 @@ export default class ConfigCommand extends BaseCommand {
for (const key of keys) {
const data = configuration.settings.get(key);

const effective = getValue(key);
const effective = configuration.getForDisplay(key);
const source = configuration.sources.get(key);

if (this.verbose) {
Expand Down Expand Up @@ -107,11 +96,11 @@ export default class ConfigCommand extends BaseCommand {
}, 0);

for (const [key, description] of keysAndDescriptions) {
report.reportInfo(null, `${key.padEnd(maxKeyLength, ` `)} ${description.padEnd(maxDescriptionLength, ` `)} ${inspect(getValue(key), inspectConfig)}`);
report.reportInfo(null, `${key.padEnd(maxKeyLength, ` `)} ${description.padEnd(maxDescriptionLength, ` `)} ${inspect(configuration.getForDisplay(key), inspectConfig)}`);
}
} else {
for (const key of keys) {
report.reportInfo(null, `${key.padEnd(maxKeyLength, ` `)} ${inspect(getValue(key), inspectConfig)}`);
report.reportInfo(null, `${key.padEnd(maxKeyLength, ` `)} ${inspect(configuration.getForDisplay(key), inspectConfig)}`);
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion packages/plugin-essentials/sources/commands/config/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,12 @@ export default class ConfigSetCommand extends BaseCommand {
[this.name]: value,
});

const updatedConfiguration = await Configuration.find(this.context.cwd, this.context.plugins);

// @ts-ignore: The Node typings forgot one field
inspect.styles.name = `cyan`;

report.reportInfo(MessageName.UNNAMED, `Successfully set ${this.name} to:\n${inspect(value, {
report.reportInfo(MessageName.UNNAMED, `Successfully set ${this.name} to:\n${inspect(updatedConfiguration.getForDisplay(this.name), {
depth: Infinity,
colors: true,
compact: false,
Expand Down
1 change: 1 addition & 0 deletions packages/yarnpkg-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@types/tunnel": "^0.0.0",
"@yarnpkg/cli": "workspace:^2.0.0-rc.32",
"@yarnpkg/plugin-link": "workspace:^2.0.0-rc.11",
"@yarnpkg/plugin-npm": "workspace:^2.0.0-rc.18",
"@yarnpkg/plugin-pnp": "workspace:^2.0.0-rc.20"
},
"scripts": {
Expand Down
47 changes: 47 additions & 0 deletions packages/yarnpkg-core/sources/Configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const IGNORED_ENV_VARIABLES = new Set([
export const ENVIRONMENT_PREFIX = `yarn_`;
export const DEFAULT_RC_FILENAME = toFilename(`.yarnrc.yml`);
export const DEFAULT_LOCK_FILENAME = toFilename(`yarn.lock`);
export const SECRET = `********`;

export enum SettingsType {
ANY = 'ANY',
Expand Down Expand Up @@ -523,6 +524,42 @@ function getDefaultValue(configuration: Configuration, definition: SettingsDefin
}
}

function hideSecrets(rawValue: unknown, definition: SettingsDefinitionNoDefault) {
if (definition.type === SettingsType.SECRET && typeof rawValue === `string`)
return SECRET;

if (definition.isArray && Array.isArray(rawValue)) {
const newValue: Array<unknown> = [];

for (const value of rawValue)
newValue.push(hideSecrets(value, definition));

return newValue;
}

if (definition.type === SettingsType.MAP && rawValue instanceof Map) {
const newValue: Map<string, unknown> = new Map();

for (const [key, value] of rawValue.entries())
newValue.set(key, hideSecrets(value, definition.valueDefinition));

return newValue;
}

if (definition.type === SettingsType.SHAPE && rawValue instanceof Map) {
const newValue: Map<string, unknown> = new Map();

for (const [key, value] of rawValue.entries()) {
const propertyDefinition = definition.properties[key];
newValue.set(key, hideSecrets(value, propertyDefinition));
}

return newValue;
}

return rawValue;
};

function getEnvironmentSettings() {
const environmentSettings: {[key: string]: any} = {};

Expand Down Expand Up @@ -929,6 +966,16 @@ export class Configuration {
return this.values.get(key) as T;
}

getForDisplay<T = any>(key: string) {
const rawValue = this.get(key);
const definition = this.settings.get(key);

if (typeof definition === `undefined`)
throw new UsageError(`Couldn't find a configuration settings named "${key}"`);

return hideSecrets(rawValue, definition) as T;
}

getSubprocessStreams(logFile: PortablePath, {header, prefix, report}: {header?: string, prefix: string, report: Report}) {
let stdout: Writable;
let stderr: Writable;
Expand Down
36 changes: 36 additions & 0 deletions packages/yarnpkg-core/tests/Configuration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {xfs, PortablePath} from '@yarnpkg/fslib';
import NpmPlugin from '@yarnpkg/plugin-npm';

import {Configuration, SECRET} from '../sources/Configuration';

async function initializeConfiguration<T>(value: unknown, cb: (dir: PortablePath) => Promise<T>) {
return await xfs.mktempPromise(async dir => {
await Configuration.updateConfiguration(dir, value);

return await cb(dir);
});
}

describe(`Configuration`, () => {
it(`should hide secrets`, async () => {
await initializeConfiguration({
npmAuthToken: `my-token`,
npmScopes: {
myScope: {
npmAuthToken: `my-token`,
},
},
}, async dir => {
const configuration = await Configuration.find(dir, {
modules: new Map([[`@yarnpkg/plugin-npm`, NpmPlugin]]),
plugins: new Set([`@yarnpkg/plugin-npm`]),
});

const firstToken = configuration.getForDisplay(`npmAuthToken`);
const secondToken = configuration.getForDisplay(`npmScopes`).get(`myScope`).get(`npmAuthToken`);

expect(firstToken).toEqual(SECRET);
expect(secondToken).toEqual(SECRET);
});
});
});
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5144,6 +5144,7 @@ __metadata:
"@yarnpkg/libzip": "workspace:^2.0.0-rc.11"
"@yarnpkg/parsers": "workspace:^2.0.0-rc.11"
"@yarnpkg/plugin-link": "workspace:^2.0.0-rc.11"
"@yarnpkg/plugin-npm": "workspace:^2.0.0-rc.18"
"@yarnpkg/plugin-pnp": "workspace:^2.0.0-rc.20"
"@yarnpkg/pnp": "workspace:^2.0.0-rc.19"
"@yarnpkg/shell": "workspace:^2.0.0-rc.11"
Expand Down

0 comments on commit 4daea78

Please sign in to comment.