diff --git a/lib/config/decrypt.js b/lib/config/decrypt.js index 94f0f7bd9d8a9465e050d5e2371d03fb04afeb6f..dccb44c2aa799176b1831ef31cb89731fe44f069 100644 --- a/lib/config/decrypt.js +++ b/lib/config/decrypt.js @@ -25,7 +25,27 @@ function decryptConfig(config, privateKey) { { token: maskToken(token) }, '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 { decryptedConfig[eKey] = decryptedStr; } diff --git a/test/config/__snapshots__/decrypt.spec.js.snap b/test/config/__snapshots__/decrypt.spec.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..df59cc5018dd7c1e54775c4a21dd8717e9f164c5 --- /dev/null +++ b/test/config/__snapshots__/decrypt.spec.js.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`config/decrypt decryptConfig() appends npm token in npmrc 1`] = ` +"foo=bar +_authToken=abcdef-ghijklm-nopqf-stuvwxyz +" +`; diff --git a/test/config/decrypt.spec.js b/test/config/decrypt.spec.js index c96616a8fe1049e9322fe795e65a5b301ec74e31..0040dd207e4201dc49292d19ef105de6692c6228 100644 --- a/test/config/decrypt.spec.js +++ b/test/config/decrypt.spec.js @@ -33,6 +33,32 @@ describe('config/decrypt', () => { expect(res.encrypted).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', () => { config.privateKey = privateKey; config.packageFiles = [ diff --git a/website/docs/private-modules.md b/website/docs/private-modules.md index 393860e880033dd627de90330886b91ea5acc613..08d7e1f05e4af9deac53b1795a94248c26211cd7 100644 --- a/website/docs/private-modules.md +++ b/website/docs/private-modules.md @@ -115,10 +115,8 @@ The configure it like: } ``` -## Future npm authentication approaches +Renovate will then use the following logic: -#### Webhooks from npm registry - -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. - -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. +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 +3. If an `npmrc` string is present but doesn't contain `${NPM_TOKEN}` then the file will have `_authToken=<token>` appended to it