├── .npmignore ├── .editorconfig ├── test ├── global-hooks.js ├── registry-url.test.js └── auth-token.test.js ├── .gitignore ├── registry-url.js ├── .github └── workflows │ └── main.yml ├── registry-url.d.ts ├── LICENSE ├── package.json ├── index.d.ts ├── README.md ├── CHANGELOG.md └── index.js /.npmignore: -------------------------------------------------------------------------------- 1 | .editorconfig 2 | .eslintignore 3 | .eslintrc 4 | .github 5 | npm-debug.log 6 | coverage 7 | test 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; editorconfig.org 2 | root = true 3 | charset= utf8 4 | 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /test/global-hooks.js: -------------------------------------------------------------------------------- 1 | exports.mochaHooks = { 2 | beforeEach () { 3 | Object.keys(process.env) 4 | .filter(envKey => /^npm_config_/i.test(envKey)) 5 | .forEach(envKey => delete process.env[envKey]) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | 4 | # Compiled files 5 | lib 6 | 7 | # Coverage directory used by tools like istanbul 8 | coverage 9 | 10 | # Dependency directories 11 | node_modules 12 | 13 | # Yarn lockfile 14 | yarn.lock 15 | 16 | -------------------------------------------------------------------------------- /registry-url.js: -------------------------------------------------------------------------------- 1 | const npmConf = require('@pnpm/npm-conf') 2 | 3 | module.exports = function getRegistryUrl (scope, npmrc) { 4 | const rc = npmrc ? { config: { get: (key) => npmrc[key] } } : npmConf() 5 | const url = rc.config.get(scope + ':registry') || rc.config.get('registry') || npmConf.defaults.registry 6 | return url.slice(-1) === '/' ? url : url + '/' 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | test: 7 | name: Node.js ${{ matrix.node-version }} 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | node-version: 13 | - 22 14 | - 20 15 | - 18 16 | - 16 17 | - 14 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - run: npm install 24 | - run: npm test 25 | -------------------------------------------------------------------------------- /registry-url.d.ts: -------------------------------------------------------------------------------- 1 | import { AuthOptions } from './'; 2 | 3 | /** 4 | * Get the registry URL for a given npm scope. Falls back to global registry if scope is not defined. 5 | * 6 | * @param [scope] - npm scope to resolve URL for. Falls back to global registry if not defined. 7 | * @param [npmrc] - Optional object of npmrc properties to use instead of looking up the users local npmrc file 8 | * @returns The resolved registry URL, falling back to the global npm registry 9 | */ 10 | declare function registryUrl( 11 | scope?: string, 12 | npmrc?: Pick 13 | ): string; 14 | 15 | export = registryUrl; 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Espen Hovlandsdal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "registry-auth-token", 3 | "version": "5.1.0", 4 | "description": "Get the auth token set for an npm registry (if any)", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha --require test/global-hooks.js", 8 | "posttest": "standard", 9 | "coverage": "istanbul cover _mocha" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+ssh://git@github.com/rexxars/registry-auth-token.git" 14 | }, 15 | "engines": { 16 | "node": ">=14" 17 | }, 18 | "keywords": [ 19 | "npm", 20 | "conf", 21 | "config", 22 | "npmconf", 23 | "registry", 24 | "auth", 25 | "token", 26 | "authtoken" 27 | ], 28 | "author": "Espen Hovlandsdal ", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/rexxars/registry-auth-token/issues" 32 | }, 33 | "homepage": "https://github.com/rexxars/registry-auth-token#readme", 34 | "dependencies": { 35 | "@pnpm/npm-conf": "^2.1.0" 36 | }, 37 | "devDependencies": { 38 | "istanbul": "^0.4.2", 39 | "mocha": "^10.0.0", 40 | "require-uncached": "^1.0.2", 41 | "standard": "^12.0.1" 42 | }, 43 | "standard": { 44 | "ignore": [ 45 | "coverage/**" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace registryAuthToken { 2 | /** 3 | * The options for passing into `registry-auth-token` 4 | */ 5 | interface AuthOptions { 6 | /** 7 | * Whether or not url's path parts are recursively trimmed from the registry 8 | * url when searching for tokens 9 | */ 10 | recursive?: boolean | undefined; 11 | /** 12 | * An npmrc configuration object used when searching for tokens. If no object is provided, 13 | * the `.npmrc` file at the base of the project is used. 14 | */ 15 | npmrc?: 16 | | { 17 | /** 18 | * A registry url used for matching 19 | */ 20 | registry?: string | undefined; 21 | /** 22 | * Registry url's with token information 23 | */ 24 | [registryUrls: string]: string | undefined; 25 | } 26 | | undefined; 27 | } 28 | /** 29 | * The generated authentication information 30 | */ 31 | interface NpmCredentials { 32 | /** 33 | * The token representing the users credentials 34 | */ 35 | token: string; 36 | /** 37 | * The type of token 38 | */ 39 | type: 'Basic' | 'Bearer'; 40 | /** 41 | * The username used in `Basic` 42 | */ 43 | username?: string | undefined; 44 | /** 45 | * The password used in `Basic` 46 | */ 47 | password?: string | undefined; 48 | } 49 | } 50 | 51 | /** 52 | * Retrieves the auth token to use for a given registry URL 53 | * 54 | * @param [registryUrl] - Either the registry url used for matching, or a configuration 55 | * object describing the contents of the .npmrc file. Uses default `registry` set in 56 | * `.npmrc` when argument is omitted. 57 | * @param [options] - a configuration object describing the 58 | * contents of the .npmrc file. If an `npmrc` config object was passed in as the 59 | * first parameter, this parameter is ignored. 60 | * @returns The `NpmCredentials` object or undefined if no match found. 61 | */ 62 | declare function registryAuthToken( 63 | registryUrl?: string | registryAuthToken.AuthOptions, 64 | options?: registryAuthToken.AuthOptions 65 | ): registryAuthToken.NpmCredentials | undefined; 66 | 67 | export = registryAuthToken; 68 | -------------------------------------------------------------------------------- /test/registry-url.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const mocha = require('mocha') 4 | const assert = require('assert') 5 | const requireUncached = require('require-uncached') 6 | 7 | const npmRcPath = path.join(__dirname, '..', '.npmrc') 8 | const afterEach = mocha.afterEach 9 | const describe = mocha.describe 10 | const it = mocha.it 11 | 12 | describe('registry-url', function () { 13 | afterEach(function (done) { 14 | fs.unlink(npmRcPath, function () { 15 | done() 16 | }) 17 | }) 18 | 19 | it('should read global if no local is found', function () { 20 | const getRegistryUrl = requireUncached('../registry-url') 21 | getRegistryUrl() 22 | }) 23 | 24 | it('should return default registry if no url is given for scope', function (done) { 25 | fs.writeFile(npmRcPath, 'registry=https://registry.npmjs.org/', function (err) { 26 | const getRegistryUrl = requireUncached('../registry-url') 27 | assert(!err, err) 28 | assert.strictEqual(getRegistryUrl('@somescope'), 'https://registry.npmjs.org/') 29 | done() 30 | }) 31 | }) 32 | 33 | it('should return registry url if url is given for scope ', function (done) { 34 | fs.writeFile(npmRcPath, '@somescope:registry=https://some.registry/', function (err) { 35 | const getRegistryUrl = requireUncached('../registry-url') 36 | assert(!err, err) 37 | assert.strictEqual(getRegistryUrl('@somescope'), 'https://some.registry/') 38 | done() 39 | }) 40 | }) 41 | 42 | it('should append trailing slash if not present', function (done) { 43 | fs.writeFile(npmRcPath, '@somescope:registry=https://some.registry', function (err) { 44 | const getRegistryUrl = requireUncached('../registry-url') 45 | assert(!err, err) 46 | assert.strictEqual(getRegistryUrl('@somescope'), 'https://some.registry/') 47 | done() 48 | }) 49 | }) 50 | 51 | it('should return configured global registry if given', function (done) { 52 | const content = [ 53 | 'registry=http://registry.foobar.eu/', 54 | '@somescope:registry=https://some.url/', '' 55 | ].join('\n') 56 | 57 | fs.writeFile(npmRcPath, content, function (err) { 58 | const getRegistryUrl = requireUncached('../registry-url') 59 | assert(!err, err) 60 | assert.strictEqual(getRegistryUrl(), 'http://registry.foobar.eu/') 61 | done() 62 | }) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # registry-auth-token 2 | 3 | [![npm version](http://img.shields.io/npm/v/registry-auth-token.svg?style=flat-square)](https://www.npmjs.com/package/registry-auth-token)[![npm](https://img.shields.io/npm/dm/registry-auth-token?style=flat-square)](https://www.npmjs.com/package/registry-auth-token) 4 | 5 | Get the auth token set for an npm registry from `.npmrc`. Also allows fetching the configured registry URL for a given npm scope. 6 | 7 | ## Installing 8 | 9 | ``` 10 | npm install --save registry-auth-token 11 | ``` 12 | 13 | ## Usage 14 | 15 | Returns an object containing `token` and `type`, or `undefined` if no token can be found. `type` can be either `Bearer` or `Basic`. 16 | 17 | ```js 18 | const getAuthToken = require('registry-auth-token') 19 | const getRegistryUrl = require('registry-auth-token/registry-url') 20 | 21 | // Get auth token and type for default `registry` set in `.npmrc` 22 | console.log(getAuthToken()) // {token: 'someToken', type: 'Bearer'} 23 | 24 | // Get auth token for a specific registry URL 25 | console.log(getAuthToken('//registry.foo.bar')) 26 | 27 | // Find the registry auth token for a given URL (with deep path): 28 | // If registry is at `//some.host/registry` 29 | // URL passed is `//some.host/registry/deep/path` 30 | // Will find token the closest matching path; `//some.host/registry` 31 | console.log(getAuthToken('//some.host/registry/deep/path', {recursive: true})) 32 | 33 | // Use the npm config that is passed in 34 | console.log(getAuthToken('//registry.foo.bar', { 35 | npmrc: { 36 | 'registry': 'http://registry.foo.bar', 37 | '//registry.foo.bar/:_authToken': 'qar' 38 | } 39 | })) 40 | 41 | // Find the configured registry url for scope `@foobar`. 42 | // Falls back to the global registry if not defined. 43 | console.log(getRegistryUrl('@foobar')) 44 | 45 | // Use the npm config that is passed in 46 | console.log(getRegistryUrl('http://registry.foobar.eu/', { 47 | 'registry': 'http://registry.foobar.eu/', 48 | '//registry.foobar.eu/:_authToken': 'qar' 49 | })) 50 | ``` 51 | 52 | ## Return value 53 | 54 | ```js 55 | // If auth info can be found: 56 | {token: 'someToken', type: 'Bearer'} 57 | 58 | // Or: 59 | {token: 'someOtherToken', type: 'Basic'} 60 | 61 | // Or, if nothing is found: 62 | undefined 63 | ``` 64 | 65 | ## Security 66 | 67 | Please be careful when using this. Leaking your auth token is dangerous. 68 | 69 | ## License 70 | 71 | MIT © [Espen Hovlandsdal](https://espen.codes/) 72 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes will be documented in this file. 4 | 5 | ## [5.1.0] - 2025-02-07 6 | 7 | ### Changes 8 | 9 | - Use WHATWG URL instead of legacy APIs, silencing errors in Bun etc (fisker Cheung) 10 | 11 | ## [5.0.3] - 2024-11-27 12 | 13 | ### Changes 14 | 15 | - Make all arguments optional in type definitions (Mattias Leino) 16 | 17 | ## [5.0.2] - 2023-03-05 18 | 19 | ### Changes 20 | 21 | - Prevent crashing on invalid npmrc files (Espen Hovlandsdal) 22 | 23 | ## [5.0.0] - 2022-06-16 24 | 25 | ### BREAKING 26 | 27 | - Require node version 14 or above (Espen Hovlandsdal) 28 | 29 | ### Added 30 | 31 | - Add typescript definitions (Espen Hovlandsdal) 32 | 33 | ### Changes 34 | 35 | - Replace outdated `rc` dependency with `@pnpm/npm-conf` (Kyler Nelson) 36 | - Fix incorrect usage information in readme (Kyler Nelson) 37 | 38 | ## [4.2.2] - 2022-06-16 39 | 40 | ### Changes 41 | 42 | - Pin version of `rc` module to `1.2.8` to avoid malware in [compromised versions](https://github.com/advisories/GHSA-g2q5-5433-rhrf) (Espen Hovlandsdal) 43 | 44 | ## [4.2.1] - 2020-11-10 45 | 46 | ### Changes 47 | 48 | - Exclude tests from published npm files (Garrit Franke) 49 | 50 | ## [4.2.0] - 2020-07-13 51 | 52 | ### Changes 53 | 54 | - Add support for `NPM_CONFIG_USERCONFIG` environment variable (Ben Sorohan) 55 | 56 | ## [4.1.0] - 2020-01-17 57 | 58 | ### Changes 59 | 60 | - Add support for legacy auth token on the registry url (Gustav Blomér) 61 | 62 | ## [4.0.0] - 2019-06-17 63 | 64 | ### BREAKING 65 | 66 | - Minimum node.js version requirement is now v6 67 | 68 | ### Changes 69 | 70 | - Upgraded dependencies (Espen Hovlandsdal) 71 | 72 | ## [3.4.0] - 2019-03-20 73 | 74 | ### Changes 75 | 76 | - Enabled legacy auth token to be read from environment variable (Martin Flodin) 77 | 78 | ## [3.3.2] - 2018-01-26 79 | 80 | ### Changes 81 | 82 | - Support password with ENV variable tokens (Nowell Strite) 83 | 84 | ## [3.3.1] - 2017-05-02 85 | 86 | ### Fixes 87 | 88 | - Auth legacy token is basic auth (Hutson Betts) 89 | 90 | ## [3.3.0] - 2017-04-24 91 | 92 | ### Changes 93 | 94 | - Support legacy auth token config key (Zoltan Kochan) 95 | - Use safe-buffer module for backwards-compatible base64 encoding/decoding (Espen Hovlandsdal) 96 | - Change to standard.js coding style (Espen Hovlandsdal) 97 | 98 | ## [3.2.0] - 2017-04-20 99 | 100 | ### Changes 101 | 102 | - Allow passing parsed npmrc from outside (Zoltan Kochan) 103 | 104 | ## [3.1.2] - 2017-04-07 105 | 106 | ### Changes 107 | 108 | - Avoid infinite loop on invalid URL (Zoltan Kochan) 109 | 110 | ## [3.1.1] - 2017-04-06 111 | 112 | ### Changes 113 | 114 | - Nerf-dart URLs even if recursive is set to false (Espen Hovlandsdal) 115 | 116 | ## [3.1.0] - 2016-10-19 117 | 118 | ### Changes 119 | 120 | - Return the password and username for Basic authorization (Zoltan Kochan) 121 | 122 | ## [3.0.1] - 2016-08-07 123 | 124 | ### Changes 125 | 126 | - Fix recursion bug (Lukas Eipert) 127 | - Implement alternative base64 encoding/decoding implementation for Node 6 (Lukas Eipert) 128 | 129 | ## [3.0.0] - 2016-08-04 130 | 131 | ### Added 132 | 133 | - Support for Basic Authentication (username/password) (Lukas Eipert) 134 | 135 | ### Changes 136 | 137 | - The result format of the output changed from a simple string to an object which contains the token type 138 | 139 | ```js 140 | // before: returns 'tokenString' 141 | // after: returns {token: 'tokenString', type: 'Bearer'} 142 | getAuthToken(); 143 | ``` 144 | 145 | ## [2.1.1] - 2016-07-10 146 | 147 | ### Changes 148 | 149 | - Fix infinite loop when recursively resolving registry URLs on Windows (Espen Hovlandsdal) 150 | 151 | ## [2.1.0] - 2016-07-07 152 | 153 | ### Added 154 | 155 | - Add feature to find configured registry URL for a scope (Espen Hovlandsdal) 156 | 157 | ## [2.0.0] - 2016-06-17 158 | 159 | ### Changes 160 | 161 | - Fix tokens defined by reference to environment variables (Dan MacTough) 162 | 163 | ## [1.1.1] - 2016-04-26 164 | 165 | ### Changes 166 | 167 | - Fix for registries with port number in URL (Ryan Day) 168 | 169 | [1.1.1]: https://github.com/rexxars/registry-auth-token/compare/a5b4fe2f5ff982110eb8a813ba1b3b3c5d851af1...v1.1.1 170 | [2.0.0]: https://github.com/rexxars/registry-auth-token/compare/v1.1.1...v2.0.0 171 | [2.1.0]: https://github.com/rexxars/registry-auth-token/compare/v2.0.0...v2.1.0 172 | [2.1.1]: https://github.com/rexxars/registry-auth-token/compare/v2.1.0...v2.1.1 173 | [3.0.0]: https://github.com/rexxars/registry-auth-token/compare/v2.1.1...v3.0.0 174 | [3.0.1]: https://github.com/rexxars/registry-auth-token/compare/v3.0.0...v3.0.1 175 | [3.1.0]: https://github.com/rexxars/registry-auth-token/compare/v3.0.1...v3.1.0 176 | [3.1.1]: https://github.com/rexxars/registry-auth-token/compare/v3.1.0...v3.1.1 177 | [3.1.2]: https://github.com/rexxars/registry-auth-token/compare/v3.1.1...v3.1.2 178 | [3.2.0]: https://github.com/rexxars/registry-auth-token/compare/v3.1.2...v3.2.0 179 | [3.3.0]: https://github.com/rexxars/registry-auth-token/compare/v3.2.0...v3.3.0 180 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const npmConf = require('@pnpm/npm-conf') 2 | 3 | const tokenKey = ':_authToken' 4 | const legacyTokenKey = ':_auth' 5 | const userKey = ':username' 6 | const passwordKey = ':_password' 7 | 8 | module.exports = function getRegistryAuthToken () { 9 | let checkUrl 10 | let options 11 | if (arguments.length >= 2) { 12 | checkUrl = arguments[0] 13 | options = Object.assign({}, arguments[1]) 14 | } else if (typeof arguments[0] === 'string') { 15 | checkUrl = arguments[0] 16 | } else { 17 | options = Object.assign({}, arguments[0]) 18 | } 19 | options = options || {} 20 | 21 | const providedNpmrc = options.npmrc 22 | options.npmrc = (options.npmrc ? { 23 | config: { 24 | get: (key) => providedNpmrc[key] 25 | } 26 | } : npmConf()).config 27 | 28 | checkUrl = checkUrl || options.npmrc.get('registry') || npmConf.defaults.registry 29 | return getRegistryAuthInfo(checkUrl, options) || getLegacyAuthInfo(options.npmrc) 30 | } 31 | 32 | // https://nodejs.org/api/url.html#urlresolvefrom-to 33 | function urlResolve (from, to) { 34 | const resolvedUrl = new URL(to, new URL(from.startsWith('//') ? `./${from}` : from, 'resolve://')) 35 | if (resolvedUrl.protocol === 'resolve:') { 36 | // `from` is a relative URL. 37 | const { pathname, search, hash } = resolvedUrl 38 | return pathname + search + hash 39 | } 40 | return resolvedUrl.toString() 41 | } 42 | 43 | function getRegistryAuthInfo (checkUrl, options) { 44 | let parsed = 45 | checkUrl instanceof URL 46 | ? checkUrl 47 | : new URL(checkUrl.startsWith('//') ? `http:${checkUrl}` : checkUrl) 48 | let pathname 49 | 50 | while (pathname !== '/' && parsed.pathname !== pathname) { 51 | pathname = parsed.pathname || '/' 52 | 53 | const regUrl = '//' + parsed.host + pathname.replace(/\/$/, '') 54 | const authInfo = getAuthInfoForUrl(regUrl, options.npmrc) 55 | if (authInfo) { 56 | return authInfo 57 | } 58 | 59 | // break if not recursive 60 | if (!options.recursive) { 61 | return /\/$/.test(checkUrl) 62 | ? undefined 63 | : getRegistryAuthInfo(new URL('./', parsed), options) 64 | } 65 | 66 | parsed.pathname = urlResolve(normalizePath(pathname), '..') || '/' 67 | } 68 | 69 | return undefined 70 | } 71 | 72 | function getLegacyAuthInfo (npmrc) { 73 | if (!npmrc.get('_auth')) { 74 | return undefined 75 | } 76 | 77 | const token = replaceEnvironmentVariable(npmrc.get('_auth')) 78 | 79 | return { token: token, type: 'Basic' } 80 | } 81 | 82 | function normalizePath (path) { 83 | return path[path.length - 1] === '/' ? path : path + '/' 84 | } 85 | 86 | function getAuthInfoForUrl (regUrl, npmrc) { 87 | // try to get bearer token 88 | const bearerAuth = getBearerToken(npmrc.get(regUrl + tokenKey) || npmrc.get(regUrl + '/' + tokenKey)) 89 | if (bearerAuth) { 90 | return bearerAuth 91 | } 92 | 93 | // try to get basic token 94 | const username = npmrc.get(regUrl + userKey) || npmrc.get(regUrl + '/' + userKey) 95 | const password = npmrc.get(regUrl + passwordKey) || npmrc.get(regUrl + '/' + passwordKey) 96 | const basicAuth = getTokenForUsernameAndPassword(username, password) 97 | if (basicAuth) { 98 | return basicAuth 99 | } 100 | 101 | const basicAuthWithToken = getLegacyAuthToken(npmrc.get(regUrl + legacyTokenKey) || npmrc.get(regUrl + '/' + legacyTokenKey)) 102 | if (basicAuthWithToken) { 103 | return basicAuthWithToken 104 | } 105 | 106 | return undefined 107 | } 108 | 109 | function replaceEnvironmentVariable (token) { 110 | return token.replace(/^\$\{?([^}]*)\}?$/, function (fullMatch, envVar) { 111 | return process.env[envVar] 112 | }) 113 | } 114 | 115 | function getBearerToken (tok) { 116 | if (!tok) { 117 | return undefined 118 | } 119 | 120 | // check if bearer token is set as environment variable 121 | const token = replaceEnvironmentVariable(tok) 122 | 123 | return { token: token, type: 'Bearer' } 124 | } 125 | 126 | function getTokenForUsernameAndPassword (username, password) { 127 | if (!username || !password) { 128 | return undefined 129 | } 130 | 131 | // passwords are base64 encoded, so we need to decode it 132 | // See https://github.com/npm/npm/blob/v3.10.6/lib/config/set-credentials-by-uri.js#L26 133 | const pass = Buffer.from(replaceEnvironmentVariable(password), 'base64').toString('utf8') 134 | 135 | // a basic auth token is base64 encoded 'username:password' 136 | // See https://github.com/npm/npm/blob/v3.10.6/lib/config/get-credentials-by-uri.js#L70 137 | const token = Buffer.from(username + ':' + pass, 'utf8').toString('base64') 138 | 139 | // we found a basicToken token so let's exit the loop 140 | return { 141 | token: token, 142 | type: 'Basic', 143 | password: pass, 144 | username: username 145 | } 146 | } 147 | 148 | function getLegacyAuthToken (tok) { 149 | if (!tok) { 150 | return undefined 151 | } 152 | 153 | // check if legacy auth token is set as environment variable 154 | const token = replaceEnvironmentVariable(tok) 155 | 156 | return { token: token, type: 'Basic' } 157 | } 158 | -------------------------------------------------------------------------------- /test/auth-token.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const mocha = require('mocha') 4 | const assert = require('assert') 5 | const requireUncached = require('require-uncached') 6 | 7 | const npmRcPath = path.join(__dirname, '..', '.npmrc') 8 | const beforeEach = mocha.beforeEach 9 | const afterEach = mocha.afterEach 10 | const describe = mocha.describe 11 | const it = mocha.it 12 | 13 | const decodeBase64 = str => Buffer.from(str, 'base64').toString('utf8') 14 | const encodeBase64 = str => Buffer.from(str, 'utf8').toString('base64') 15 | 16 | /* eslint max-nested-callbacks: ["error", 4] */ 17 | 18 | describe('auth-token', function () { 19 | afterEach(function (done) { 20 | fs.unlink(npmRcPath, function () { 21 | done() 22 | }) 23 | }) 24 | 25 | it('should read global if no local is found', function () { 26 | const getAuthToken = requireUncached('../index') 27 | getAuthToken() 28 | }) 29 | 30 | it('should return undefined if no auth token is given for registry', function (done) { 31 | fs.writeFile(npmRcPath, 'registry=http://registry.npmjs.eu/', function (err) { 32 | const getAuthToken = requireUncached('../index') 33 | assert(!err, err) 34 | assert(!getAuthToken()) 35 | done() 36 | }) 37 | }) 38 | 39 | describe('legacy auth token', function () { 40 | it('should return auth token if it is defined in the legacy way via the `_auth` key', function (done) { 41 | const content = [ 42 | '_auth=foobar', 43 | 'registry=http://registry.foobar.eu/' 44 | ].join('\n') 45 | 46 | fs.writeFile(npmRcPath, content, function (err) { 47 | const getAuthToken = requireUncached('../index') 48 | assert(!err, err) 49 | assert.deepStrictEqual(getAuthToken(), { token: 'foobar', type: 'Basic' }) 50 | done() 51 | }) 52 | }) 53 | 54 | it('should return legacy auth token defined by reference to an environment variable (with curly braces)', function (done) { 55 | const environmentVariable = '__REGISTRY_AUTH_TOKEN_NPM_TOKEN__' 56 | const content = [ 57 | '_auth=${' + environmentVariable + '}', 58 | 'registry=http://registry.foobar.eu/' 59 | ].join('\n') 60 | 61 | process.env[environmentVariable] = 'foobar' 62 | 63 | fs.writeFile(npmRcPath, content, function (err) { 64 | const getAuthToken = requireUncached('../index') 65 | assert(!err, err) 66 | assert.deepStrictEqual(getAuthToken(), { token: 'foobar', type: 'Basic' }) 67 | delete process.env[environmentVariable] 68 | done() 69 | }) 70 | }) 71 | 72 | it('should return legacy auth token defined by reference to an environment variable (without curly braces)', function (done) { 73 | const environmentVariable = '__REGISTRY_AUTH_TOKEN_NPM_TOKEN__' 74 | const content = [ 75 | '_auth=$' + environmentVariable, 76 | 'registry=http://registry.foobar.eu/' 77 | ].join('\n') 78 | 79 | process.env[environmentVariable] = 'foobar' 80 | 81 | fs.writeFile(npmRcPath, content, function (err) { 82 | const getAuthToken = requireUncached('../index') 83 | assert(!err, err) 84 | assert.deepStrictEqual(getAuthToken(), { token: 'foobar', type: 'Basic' }) 85 | delete process.env[environmentVariable] 86 | done() 87 | }) 88 | }) 89 | }) 90 | 91 | describe('bearer token', function () { 92 | it('should return auth token if registry is defined', function (done) { 93 | const content = [ 94 | 'registry=http://registry.foobar.eu/', 95 | '//registry.foobar.eu/:_authToken=foobar', '' 96 | ].join('\n') 97 | 98 | fs.writeFile(npmRcPath, content, function (err) { 99 | const getAuthToken = requireUncached('../index') 100 | assert(!err, err) 101 | assert.deepStrictEqual(getAuthToken(), { token: 'foobar', type: 'Bearer' }) 102 | done() 103 | }) 104 | }) 105 | 106 | it('should use npmrc passed in', function (done) { 107 | const content = [ 108 | 'registry=http://registry.foobar.eu/', 109 | '//registry.foobar.eu/:_authToken=foobar', '' 110 | ].join('\n') 111 | 112 | fs.writeFile(npmRcPath, content, function (err) { 113 | const getAuthToken = requireUncached('../index') 114 | assert(!err, err) 115 | const npmrc = { 116 | 'registry': 'http://registry.foobar.eu/', 117 | '//registry.foobar.eu/:_authToken': 'qar' 118 | } 119 | assert.deepStrictEqual(getAuthToken({ npmrc: npmrc }), { token: 'qar', type: 'Bearer' }) 120 | done() 121 | }) 122 | }) 123 | 124 | it('should return auth token if registry url has port specified', function (done) { 125 | const content = [ 126 | 'registry=http://localhost:8770/', 127 | // before the patch this token was selected. 128 | '//localhost/:_authToken=ohno', 129 | '//localhost:8770/:_authToken=beepboop', '' 130 | ].join('\n') 131 | 132 | fs.writeFile(npmRcPath, content, function (err) { 133 | const getAuthToken = requireUncached('../index') 134 | assert(!err, err) 135 | assert.deepStrictEqual(getAuthToken(), { token: 'beepboop', type: 'Bearer' }) 136 | done() 137 | }) 138 | }) 139 | 140 | it('should return auth token defined by reference to an environment variable (with curly braces)', function (done) { 141 | const environmentVariable = '__REGISTRY_AUTH_TOKEN_NPM_TOKEN__' 142 | const content = [ 143 | 'registry=http://registry.foobar.cc/', 144 | '//registry.foobar.cc/:_authToken=${' + environmentVariable + '}', '' 145 | ].join('\n') 146 | process.env[environmentVariable] = 'foobar' 147 | 148 | fs.writeFile(npmRcPath, content, function (err) { 149 | const getAuthToken = requireUncached('../index') 150 | assert(!err, err) 151 | assert.deepStrictEqual(getAuthToken(), { token: 'foobar', type: 'Bearer' }) 152 | delete process.env[environmentVariable] 153 | done() 154 | }) 155 | }) 156 | 157 | it('should return auth token defined by reference to an environment variable (without curly braces)', function (done) { 158 | const environmentVariable = '__REGISTRY_AUTH_TOKEN_NPM_TOKEN__' 159 | const content = [ 160 | 'registry=http://registry.foobar.cc/', 161 | '//registry.foobar.cc/:_authToken=$' + environmentVariable, '' 162 | ].join('\n') 163 | process.env[environmentVariable] = 'foobar' 164 | 165 | fs.writeFile(npmRcPath, content, function (err) { 166 | const getAuthToken = requireUncached('../index') 167 | assert(!err, err) 168 | assert.deepStrictEqual(getAuthToken(), { token: 'foobar', type: 'Bearer' }) 169 | delete process.env[environmentVariable] 170 | done() 171 | }) 172 | }) 173 | 174 | it('should try with and without a slash at the end of registry url', function (done) { 175 | const content = [ 176 | 'registry=http://registry.foobar.eu', 177 | '//registry.foobar.eu:_authToken=barbaz', '' 178 | ].join('\n') 179 | 180 | fs.writeFile(npmRcPath, content, function (err) { 181 | const getAuthToken = requireUncached('../index') 182 | assert(!err, err) 183 | assert.deepStrictEqual(getAuthToken(), { token: 'barbaz', type: 'Bearer' }) 184 | done() 185 | }) 186 | }) 187 | 188 | it('should fetch for the registry given (if defined)', function (done) { 189 | const content = [ 190 | '//registry.foobar.eu:_authToken=barbaz', 191 | '//registry.blah.foo:_authToken=whatev', 192 | '//registry.last.thing:_authToken=yep', '' 193 | ].join('\n') 194 | 195 | fs.writeFile(npmRcPath, content, function (err) { 196 | const getAuthToken = requireUncached('../index') 197 | assert(!err, err) 198 | assert.deepStrictEqual(getAuthToken('//registry.blah.foo'), { token: 'whatev', type: 'Bearer' }) 199 | done() 200 | }) 201 | }) 202 | 203 | it('recursively finds registries for deep url if option is set', function (done, undef) { 204 | const opts = { recursive: true } 205 | const content = [ 206 | '//registry.blah.com/foo:_authToken=whatev', 207 | '//registry.blah.org/foo/bar:_authToken=recurseExactlyOneLevel', 208 | '//registry.blah.edu/foo/bar/baz:_authToken=recurseNoLevel', 209 | '//registry.blah.eu:_authToken=yep', '' 210 | ].join('\n') 211 | 212 | fs.writeFile(npmRcPath, content, function (err) { 213 | const getAuthToken = requireUncached('../index') 214 | assert(!err, err) 215 | assert.deepStrictEqual(getAuthToken('https://registry.blah.edu/foo/bar/baz', opts), { token: 'recurseNoLevel', type: 'Bearer' }) 216 | assert.deepStrictEqual(getAuthToken('https://registry.blah.org/foo/bar/baz', opts), { token: 'recurseExactlyOneLevel', type: 'Bearer' }) 217 | assert.deepStrictEqual(getAuthToken('https://registry.blah.com/foo/bar/baz', opts), { token: 'whatev', type: 'Bearer' }) 218 | assert.deepStrictEqual(getAuthToken('http://registry.blah.eu/what/ever', opts), { token: 'yep', type: 'Bearer' }) 219 | assert.deepStrictEqual(getAuthToken('http://registry.blah.eu//what/ever', opts), undefined, 'does not hang') 220 | assert.strictEqual(getAuthToken('//some.registry', opts), undef) 221 | done() 222 | }) 223 | }) 224 | 225 | it('should try both with and without trailing slash', function (done) { 226 | fs.writeFile(npmRcPath, '//registry.blah.com:_authToken=whatev', function (err) { 227 | const getAuthToken = requireUncached('../index') 228 | assert(!err, err) 229 | assert.deepStrictEqual(getAuthToken('https://registry.blah.com'), { token: 'whatev', type: 'Bearer' }) 230 | done() 231 | }) 232 | }) 233 | 234 | it('should prefer bearer token over basic token', function (done) { 235 | const content = [ 236 | 'registry=http://registry.foobar.eu/', 237 | 'registry=http://registry.foobar.eu/', 238 | '//registry.foobar.eu/:_authToken=bearerToken', 239 | '//registry.foobar.eu/:_password=' + encodeBase64('foobar'), 240 | '//registry.foobar.eu/:username=foobar', '' 241 | ].join('\n') 242 | 243 | fs.writeFile(npmRcPath, content, function (err) { 244 | const getAuthToken = requireUncached('../index') 245 | assert(!err, err) 246 | assert.deepStrictEqual(getAuthToken('//registry.foobar.eu'), { token: 'bearerToken', type: 'Bearer' }) 247 | done() 248 | }) 249 | }) 250 | 251 | it('"nerf darts" registry urls', function (done, undef) { 252 | fs.writeFile(npmRcPath, '//contoso.pkgs.visualstudio.com/_packaging/MyFeed/npm/:_authToken=heider', function (err) { 253 | const getAuthToken = requireUncached('../index') 254 | assert(!err, err) 255 | assert.deepStrictEqual( 256 | getAuthToken('https://contoso.pkgs.visualstudio.com/_packaging/MyFeed/npm/registry'), 257 | { token: 'heider', type: 'Bearer' } 258 | ) 259 | done() 260 | }) 261 | }) 262 | }) 263 | 264 | describe('basic token', function () { 265 | it('should return undefined if password or username are missing', function (done, undef) { 266 | const content = [ 267 | 'registry=http://registry.foobar.eu/', 268 | '//registry.foobar.eu/:_password=' + encodeBase64('foobar'), 269 | '//registry.foobar.com/:username=foobar', '' 270 | ].join('\n') 271 | 272 | fs.writeFile(npmRcPath, content, function (err) { 273 | const getAuthToken = requireUncached('../index') 274 | assert(!err, err) 275 | assert.strictEqual(getAuthToken('//registry.foobar.eu'), undef) 276 | assert.strictEqual(getAuthToken('//registry.foobar.com'), undef) 277 | done() 278 | }) 279 | }) 280 | 281 | it('should return basic token if username and password are defined', function (done) { 282 | const content = [ 283 | 'registry=http://registry.foobar.eu/', 284 | '//registry.foobar.eu/:_password=' + encodeBase64('foobar'), 285 | '//registry.foobar.eu/:username=foobar', '' 286 | ].join('\n') 287 | 288 | fs.writeFile(npmRcPath, content, function (err) { 289 | const getAuthToken = requireUncached('../index') 290 | assert(!err, err) 291 | const token = getAuthToken() 292 | assert.deepStrictEqual(token, { 293 | token: 'Zm9vYmFyOmZvb2Jhcg==', 294 | type: 'Basic', 295 | username: 'foobar', 296 | password: 'foobar' 297 | }) 298 | assert.strictEqual(decodeBase64(token.token), 'foobar:foobar') 299 | done() 300 | }) 301 | }) 302 | 303 | it('should return basic token if _auth is base64 encoded', function (done) { 304 | const content = [ 305 | 'registry=http://registry.foobar.eu/', 306 | '//registry.foobar.eu/:_auth=' + encodeBase64('foobar:foobar') 307 | ].join('\n') 308 | 309 | fs.writeFile(npmRcPath, content, function (err) { 310 | const getAuthToken = requireUncached('../index') 311 | assert(!err, err) 312 | const token = getAuthToken() 313 | assert.deepStrictEqual(token, { 314 | token: 'Zm9vYmFyOmZvb2Jhcg==', 315 | type: 'Basic' 316 | }) 317 | assert.strictEqual(decodeBase64(token.token), 'foobar:foobar') 318 | done() 319 | }) 320 | }) 321 | 322 | it('should return basic token if registry url has port specified', function (done) { 323 | const content = [ 324 | 'registry=http://localhost:8770/', 325 | // before the patch this token was selected. 326 | '//localhost/:_authToken=ohno', 327 | '//localhost:8770/:_password=' + encodeBase64('foobar'), 328 | '//localhost:8770/:username=foobar', '' 329 | ].join('\n') 330 | 331 | fs.writeFile(npmRcPath, content, function (err) { 332 | const getAuthToken = requireUncached('../index') 333 | assert(!err, err) 334 | const token = getAuthToken() 335 | assert.deepStrictEqual(token, { 336 | token: 'Zm9vYmFyOmZvb2Jhcg==', 337 | type: 'Basic', 338 | username: 'foobar', 339 | password: 'foobar' 340 | }) 341 | assert.strictEqual(decodeBase64(token.token), 'foobar:foobar') 342 | done() 343 | }) 344 | }) 345 | 346 | it('should return password defined by reference to an environment variable (with curly braces)', function (done) { 347 | const environmentVariable = '__REGISTRY_PASSWORD__' 348 | const content = [ 349 | 'registry=http://registry.foobar.cc/', 350 | '//registry.foobar.cc/:username=username', 351 | '//registry.foobar.cc/:_password=${' + environmentVariable + '}', '' 352 | ].join('\n') 353 | process.env[environmentVariable] = encodeBase64('password') 354 | 355 | fs.writeFile(npmRcPath, content, function (err) { 356 | const getAuthToken = requireUncached('../index') 357 | assert(!err, err) 358 | const token = getAuthToken() 359 | assert.deepStrictEqual(token, { 360 | type: 'Basic', 361 | username: 'username', 362 | password: 'password', 363 | token: 'dXNlcm5hbWU6cGFzc3dvcmQ=' 364 | }) 365 | assert.strictEqual(decodeBase64(token.token), 'username:password') 366 | delete process.env[environmentVariable] 367 | done() 368 | }) 369 | }) 370 | 371 | it('should return password defined by reference to an environment variable (without curly braces)', function (done) { 372 | const environmentVariable = '__REGISTRY_PASSWORD__' 373 | const content = [ 374 | 'registry=http://registry.foobar.cc/', 375 | '//registry.foobar.cc/:username=username', 376 | '//registry.foobar.cc/:_password=$' + environmentVariable, '' 377 | ].join('\n') 378 | process.env[environmentVariable] = encodeBase64('password') 379 | 380 | fs.writeFile(npmRcPath, content, function (err) { 381 | const getAuthToken = requireUncached('../index') 382 | assert(!err, err) 383 | const token = getAuthToken() 384 | assert.deepStrictEqual(token, { 385 | type: 'Basic', 386 | username: 'username', 387 | password: 'password', 388 | token: 'dXNlcm5hbWU6cGFzc3dvcmQ=' 389 | }) 390 | assert.strictEqual(decodeBase64(token.token), 'username:password') 391 | delete process.env[environmentVariable] 392 | done() 393 | }) 394 | }) 395 | 396 | it('should try with and without a slash at the end of registry url', function (done) { 397 | const content = [ 398 | 'registry=http://registry.foobar.eu', 399 | '//registry.foobar.eu:_password=' + encodeBase64('barbay'), 400 | '//registry.foobar.eu:username=barbaz', '' 401 | ].join('\n') 402 | 403 | fs.writeFile(npmRcPath, content, function (err) { 404 | const getAuthToken = requireUncached('../index') 405 | assert(!err, err) 406 | const token = getAuthToken() 407 | assert.deepStrictEqual(token, { 408 | token: 'YmFyYmF6OmJhcmJheQ==', 409 | type: 'Basic', 410 | password: 'barbay', 411 | username: 'barbaz' 412 | }) 413 | assert.strictEqual(decodeBase64(token.token), 'barbaz:barbay') 414 | done() 415 | }) 416 | }) 417 | 418 | it('should fetch for the registry given (if defined)', function (done) { 419 | const content = [ 420 | '//registry.foobar.eu:_authToken=barbaz', 421 | '//registry.blah.foo:_password=' + encodeBase64('barbay'), 422 | '//registry.blah.foo:username=barbaz', 423 | '//registry.last.thing:_authToken=yep', '' 424 | ].join('\n') 425 | 426 | fs.writeFile(npmRcPath, content, function (err) { 427 | const getAuthToken = requireUncached('../index') 428 | assert(!err, err) 429 | const token = getAuthToken('//registry.blah.foo') 430 | assert.deepStrictEqual(token, { 431 | token: 'YmFyYmF6OmJhcmJheQ==', 432 | type: 'Basic', 433 | password: 'barbay', 434 | username: 'barbaz' 435 | }) 436 | assert.strictEqual(decodeBase64(token.token), 'barbaz:barbay') 437 | done() 438 | }) 439 | }) 440 | 441 | it('recursively finds registries for deep url if option is set', function (done, undef) { 442 | const opts = { recursive: true } 443 | const content = [ 444 | '//registry.blah.com/foo:_password=' + encodeBase64('barbay'), 445 | '//registry.blah.com/foo:username=barbaz', 446 | '//registry.blah.eu:username=barbaz', 447 | '//registry.blah.eu:_password=' + encodeBase64('foobaz'), '' 448 | ].join('\n') 449 | 450 | fs.writeFile(npmRcPath, content, function (err) { 451 | const getAuthToken = requireUncached('../index') 452 | assert(!err, err) 453 | let token = getAuthToken('https://registry.blah.com/foo/bar/baz', opts) 454 | assert.deepStrictEqual(token, { 455 | token: 'YmFyYmF6OmJhcmJheQ==', 456 | type: 'Basic', 457 | password: 'barbay', 458 | username: 'barbaz' 459 | }) 460 | assert.strictEqual(decodeBase64(token.token), 'barbaz:barbay') 461 | token = getAuthToken('https://registry.blah.eu/foo/bar/baz', opts) 462 | assert.deepStrictEqual(token, { 463 | token: 'YmFyYmF6OmZvb2Jheg==', 464 | type: 'Basic', 465 | password: 'foobaz', 466 | username: 'barbaz' 467 | }) 468 | assert.strictEqual(decodeBase64(token.token), 'barbaz:foobaz') 469 | assert.strictEqual(getAuthToken('//some.registry', opts), undef) 470 | done() 471 | }) 472 | }) 473 | }) 474 | 475 | describe('npmrc file resolution', function () { 476 | let npmRcPath 477 | beforeEach(function () { 478 | process.env.npm_config_userconfig = '' 479 | process.env.NPM_CONFIG_USERCONFIG = '' 480 | }) 481 | 482 | afterEach(function (done) { 483 | process.env.npm_config_userconfig = '' 484 | process.env.NPM_CONFIG_USERCONFIG = '' 485 | fs.unlink(npmRcPath, function () { 486 | done() 487 | }) 488 | }) 489 | 490 | it('should use npmrc from environment npm_config_userconfig', function (done) { 491 | const content = [ 492 | 'registry=http://registry.foobar.eu/', 493 | '//registry.foobar.eu/:_authToken=foobar', '' 494 | ].join('\n') 495 | 496 | npmRcPath = path.join(__dirname, '..', '.npmrc.env') 497 | process.env.NPM_CONFIG_USERCONFIG = npmRcPath 498 | fs.writeFile(npmRcPath, content, function (err) { 499 | const getAuthToken = requireUncached('../index') 500 | assert(!err, err) 501 | assert.deepStrictEqual(getAuthToken(), { token: 'foobar', type: 'Bearer' }) 502 | done() 503 | }) 504 | }) 505 | }) 506 | }) 507 | --------------------------------------------------------------------------------