google-artifactregistry-auth
Version:
google-artifactregistry-auth is an npm module that allows you to configure npm to interact with npm repositories stored in Artifact Registry.
152 lines (138 loc) • 6.23 kB
JavaScript
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const c = require('./config');
const {logger} = require('./logger');
/**
* Update the project and user npmrc files.
*
* @param {string} fromConfigPath Path to the npmrc file to read scope registry configs from, should be the project npmrc file.
* @param {string} toConfigPath Path to npmrc file to write authentication configs to, should be the user npmrc file.
* @param {string} creds Encrypted credentials.
* @param {boolean} allowAllDomains Set if allow all domains.
* @return {!Promise<undefined>}
*/
async function updateConfigFiles(fromConfigPath, toConfigPath, creds, allowAllDomains) {
fromConfigPath = path.resolve(fromConfigPath);
toConfigPath = path.resolve(toConfigPath);
const fromConfigs = [];
const toConfigs = [];
const registryAuthConfigs = new Map();
// We do not use basic auth any more in `gcloud artifacts print-settings`; replace them.
let fromConfigLines = await fs.promises.readFile(fromConfigPath, "utf8")
const legacyRegex = /(\/\/[a-zA-Z1-9-]+[-]npm[.]pkg[.]dev\/.*\/):_password=.*(\n\/\/[a-zA-Z1-9-]+[-]npm[.]pkg[.]dev\/.*\/:username=oauth2accesstoken)/g;
fromConfigLines = fromConfigLines.replace(legacyRegex, `$1:_authToken=${creds}`)
// Read configs from project npmrc file. For each:
// - registry config, create an auth token config in the user npmrc file (expect an auth token or password config already exists)
// - auth token config, print a warning and remove it.
// - password config, print a warning and move it to the user npmrc file.
// - everything else, keep it in the project npmrc file.
for (const line of fromConfigLines.split('\n')) {
let config = c.parseConfig(line.trim(), allowAllDomains);
switch (config.type) {
case c.configType.Registry:
fromConfigs.push(config);
registryAuthConfigs.set(config.registry, {
type: c.configType.AuthToken,
registry: config.registry,
token: creds,
toString: function() {
return `${this.registry}:_authToken=${this.token}`;
}
});
break;
case c.configType.AuthToken:
logger.debug(`Found an auth token for the registry ${config.registry} in the project npmrc file. Moving it to the user npmrc file...`);
break;
case c.configType.Password:
logger.debug(`Found password for the registry ${config.registry} in the project npmrc file. Moving it to the user npmrc file...`);
registryAuthConfigs.set(config.registry, config);
break;
default:
fromConfigs.push(config);
}
}
if (fs.existsSync(toConfigPath)) {
const toConfigLines = await fs.promises.readFile(toConfigPath, "utf8")
// refresh tokens for all auth token configs; keep everything else unchanged.
for (const line of toConfigLines.split('\n')) {
if (line == "") {
continue;
}
let config = c.parseConfig(line.trim(), allowAllDomains);
if (config.type == c.configType.AuthToken || config.type == c.configType.Password) {
registryAuthConfigs.delete(config.registry);
}
// refresh the token.
if (config.type == c.configType.AuthToken) {
config.token = creds;
}
toConfigs.push(config);
}
}
// Registries that we need to move password configs from the project npmrc file
// or write a new auth token config.
toConfigs.push(...registryAuthConfigs.values());
// Write to the user npmrc file first so that if it failed the project npmrc file
// would still be untouched.
await fs.promises.writeFile(toConfigPath, toConfigs.join(`\n`));
if(fromConfigPath !== toConfigPath) {
// If the files are the same (and likely the user .npmrc file) only write once with the auth configs
// Otherwise, we'd overwrite this file without adding the credentials
await fs.promises.writeFile(fromConfigPath, fromConfigs.join(`\n`));
}
}
/**
* Update an npmrc file with credentials.
*
* @deprecated
* @param {string} configPath Path to npmrc file.
* @param {string} creds Encrypted credentials.
* @return {!Promise<undefined>}
*/
async function updateConfigFile(configPath, creds) {
contents = await fs.promises.readFile(configPath, 'utf8')
const regex = /(\/\/[a-zA-Z1-9-]+[-]npm[.]pkg[.]dev\/.*\/:_authToken=).*/g;
const legacyRegex =
/(\/\/[a-zA-Z1-9-]+[-]npm[.]pkg[.]dev\/.*\/:_password=).*(\n\/\/[a-zA-Z1-9-]+[-]npm[.]pkg[.]dev\/.*\/:username=oauth2accesstoken)/g;
const prefixRegex = /\/\/[a-zA-Z1-9-]+[-]npm[.]pkg[.]dev\/.*\//;
let newContents;
// If config is basic auth, encrypt the token.
if (contents.match(legacyRegex)) {
encrypted_creds = Buffer.from(creds).toString('base64');
newContents = contents.replace(legacyRegex, `$1"${encrypted_creds}"$2`);
contents = newContents;
}
else if (!contents.match(regex)) {
// _authToken may have been moved to user .npmrc but may now need to be used in local/project .npmrc
// so if possible add back to local/project .npmrc
const prefixMatch = contents.match(prefixRegex)
if (prefixMatch) {
contents = `${contents}\n${prefixMatch[0]}:_authToken=""`
} else {
throw new Error(
'Artifact Registry config not found in ' + configPath +
'\nRun `gcloud beta artifacts print-settings npm`.');
}
}
newContents = contents.replace(regex, `$1"${creds}"`);
const tempConfigPath = configPath.replace('.npmrc', '.npmrc-temp');
await fs.promises.writeFile(configPath, newContents);
}
module.exports = {
updateConfigFiles,
updateConfigFile
};