Skip to content
Snippets Groups Projects
Commit 6643a3d0 authored by Rhys Arkins's avatar Rhys Arkins
Browse files

feat: npm token substitution in npmrc

If an encrypted npmToken is found alongside an unencrypted npmrc in config, then the token will replace any `${NPM_TOKEN}` placeholder found, or be appended to the end of the file. This enables large npmrc files to be defined in config without needing to enrypt the entire thing.

Closes #1796
parent b2183fe0
No related branches found
No related tags found
No related merge requests found
...@@ -25,7 +25,27 @@ function decryptConfig(config, privateKey) { ...@@ -25,7 +25,27 @@ function decryptConfig(config, privateKey) {
{ token: maskToken(token) }, { token: maskToken(token) },
'Migrating npmToken to npmrc' 'Migrating npmToken to npmrc'
); );
decryptedConfig.npmrc = `//registry.npmjs.org/:_authToken=${token}\n`; if (decryptedConfig.npmrc) {
/* eslint-disable no-template-curly-in-string */
if (decryptedConfig.npmrc.includes('${NPM_TOKEN}')) {
logger.debug('Replacing ${NPM_TOKEN} with decrypted token');
decryptedConfig.npmrc = decryptedConfig.npmrc.replace(
'${NPM_TOKEN}',
token
);
} else {
logger.debug(
'Appending _authToken= to end of existing npmrc'
);
decryptedConfig.npmrc = decryptedConfig.npmrc.replace(
/\n?$/,
`\n_authToken=${token}\n`
);
}
/* eslint-enable no-template-curly-in-string */
} else {
decryptedConfig.npmrc = `//registry.npmjs.org/:_authToken=${token}\n`;
}
} else { } else {
decryptedConfig[eKey] = decryptedStr; decryptedConfig[eKey] = decryptedStr;
} }
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`config/decrypt decryptConfig() appends npm token in npmrc 1`] = `
"foo=bar
_authToken=abcdef-ghijklm-nopqf-stuvwxyz
"
`;
...@@ -33,6 +33,32 @@ describe('config/decrypt', () => { ...@@ -33,6 +33,32 @@ describe('config/decrypt', () => {
expect(res.encrypted).not.toBeDefined(); expect(res.encrypted).not.toBeDefined();
expect(res.a).not.toBeDefined(); expect(res.a).not.toBeDefined();
}); });
it('replaces npm token placeholder in npmrc', () => {
config.privateKey = privateKey;
config.npmrc = '//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n'; // eslint-disable-line no-template-curly-in-string
config.encrypted = {
npmToken:
'FLA9YHIzpE7YetAg/P0X46npGRCMqn7hgyzwX5ZQ9wYgu9BRRbTiBVsUIFTyM5BuP1Q22slT2GkWvFvum7GU236Y6QiT7Nr8SLvtsJn2XUuq8H7REFKzdy3+wqyyWbCErYTFyY1dcPM7Ht+CaGDWdd8u/FsoX7AdMRs/X1jNUo6iSmlUiyGlYDKF+QMnCJom1VPVgZXWsGKdjI2MLny991QMaiv0VajmFIh4ENv4CtXOl/1twvIl/6XTXAaqpJJKDTPZEuydi+PHDZmal2RAOfrkH4m0UURa7SlfpUlIg+EaqbNGp85hCYXLwRcEET1OnYr3rH1oYkcYJ40any1tvQ==',
};
const res = decryptConfig(config, privateKey);
expect(res.encrypted).not.toBeDefined();
expect(res.npmToken).not.toBeDefined();
expect(res.npmrc).toEqual(
'//registry.npmjs.org/:_authToken=abcdef-ghijklm-nopqf-stuvwxyz\n'
);
});
it('appends npm token in npmrc', () => {
config.privateKey = privateKey;
config.npmrc = 'foo=bar\n'; // eslint-disable-line no-template-curly-in-string
config.encrypted = {
npmToken:
'FLA9YHIzpE7YetAg/P0X46npGRCMqn7hgyzwX5ZQ9wYgu9BRRbTiBVsUIFTyM5BuP1Q22slT2GkWvFvum7GU236Y6QiT7Nr8SLvtsJn2XUuq8H7REFKzdy3+wqyyWbCErYTFyY1dcPM7Ht+CaGDWdd8u/FsoX7AdMRs/X1jNUo6iSmlUiyGlYDKF+QMnCJom1VPVgZXWsGKdjI2MLny991QMaiv0VajmFIh4ENv4CtXOl/1twvIl/6XTXAaqpJJKDTPZEuydi+PHDZmal2RAOfrkH4m0UURa7SlfpUlIg+EaqbNGp85hCYXLwRcEET1OnYr3rH1oYkcYJ40any1tvQ==',
};
const res = decryptConfig(config, privateKey);
expect(res.encrypted).not.toBeDefined();
expect(res.npmToken).not.toBeDefined();
expect(res.npmrc).toMatchSnapshot();
});
it('decrypts nested', () => { it('decrypts nested', () => {
config.privateKey = privateKey; config.privateKey = privateKey;
config.packageFiles = [ config.packageFiles = [
......
...@@ -115,10 +115,8 @@ The configure it like: ...@@ -115,10 +115,8 @@ The configure it like:
} }
``` ```
## Future npm authentication approaches Renovate will then use the following logic:
#### Webhooks from npm registry 1. If no `npmrc` string is present in config then one will be created with the `_authToken` pointing to the default npmjs registry
2. If an `npmrc` string is present and contains `${NPM_TOKEN}` then that placeholder will be replaced with the decrypted token
The npm registry allows for owners of packages to send webhooks to custom destinations whenever the package is updated. Using this approach, it would be possible to notify the Renovate App API of updates to your private npm modules and we store these in our database. 3. If an `npmrc` string is present but doesn't contain `${NPM_TOKEN}` then the file will have `_authToken=<token>` appended to it
An important downside of this approach to be aware of is that this could solve only Use #1 (module lookup) and not Use #2 (Lock file generation). As it seems inevitable that most projects will adopt lock files - especially projects advanced enough to be using private npm modules - this solution is taking a lower priority compared to the first two, because it may ultimately not be required if lock file support becomes as widespread as expected.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment