-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix scoped pkg auth when using npm private pkgs via npm login --scope #5162
Changes from 1 commit
a05fc91
93dd2ab
9dcbc8f
ea3fba0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -197,69 +197,31 @@ describe('request', () => { | |
|
||
npmRegistry.config = { | ||
'//some.other.registry/:_authToken': 'testScopedAuthToken', | ||
'@testScope:registry': '//some.other.registry/', | ||
'@testScope:registry': 'https://some.other.registry/', | ||
}; | ||
npmRegistry.request(url); | ||
|
||
const requestParams = mockRequestManager.request.mock.calls[0][0]; | ||
|
||
expect(requestParams.headers.authorization).toBe('Bearer testScopedAuthToken'); | ||
}); | ||
}); | ||
|
||
describe('isRequestToRegistry functional test', () => { | ||
test('request to registry url matching', () => { | ||
test('should add authorization header with token for default registry when using npm login --scope=@foo', () => { | ||
const testCwd = '.'; | ||
const {mockRequestManager, mockRegistries, mockReporter} = createMocks(); | ||
const npmRegistry = new NpmRegistry(testCwd, mockRegistries, mockRequestManager, mockReporter); | ||
|
||
const validRegistryUrls = [ | ||
['http://foo.bar:80/foo/bar/baz', 'http://foo.bar/foo/'], | ||
['http://foo.bar:80/foo/bar/baz', 'https://foo.bar/foo/'], | ||
['http://foo.bar/foo/bar/baz', 'http://foo.bar/foo/'], | ||
['http://foo.bar/foo/00000000-1111-4444-8888-000000000000/baz', 'http://foo.bar/foo/'], | ||
['https://foo.bar:443/foo/bar/baz', 'https://foo.bar/foo/'], | ||
['http://foo.bar/foo/bar/baz', 'https://foo.bar:443/foo/'], | ||
['https://foo.bar/foo/bar/baz', 'https://foo.bar:443/foo/'], | ||
['HTTP://xn--xample-hva.com:80/foo/bar/baz', 'http://锚xample.com/foo/bar/baz'], | ||
]; | ||
|
||
const invalidRegistryUrls = [ | ||
['https://wrong.thing/foo/bar/baz', 'https://foo.bar/foo/'], | ||
['https://foo.bar:1337/foo/bar/baz', 'https://foo.bar/foo/'], | ||
]; | ||
|
||
validRegistryUrls.forEach(([requestUrl, registryUrl]) => | ||
expect(npmRegistry.isRequestToRegistry(requestUrl, registryUrl)).toBe(true), | ||
); | ||
invalidRegistryUrls.forEach(([requestUrl, registryUrl]) => | ||
expect(npmRegistry.isRequestToRegistry(requestUrl, registryUrl)).toBe(false), | ||
); | ||
}); | ||
|
||
test('isRequestToRegistry with custom host prefix', () => { | ||
const testCwd = '.'; | ||
const {mockRequestManager, mockRegistries, mockReporter} = createMocks(); | ||
const npmRegistry = new NpmRegistry(testCwd, mockRegistries, mockRequestManager, mockReporter); | ||
const url = 'https://npmjs.registry.org/@foo%2fyarn.tgz'; | ||
|
||
npmRegistry.config = { | ||
'custom-host-suffix': 'some.host.org', | ||
'//npmjs.registry.org/:_authToken': 'testScopedAuthToken', | ||
'@foo:registry': 'https://npmjs.registry.org/', | ||
}; | ||
npmRegistry.request(url); | ||
|
||
expect(npmRegistry.isRequestToRegistry('http://pkgs.host.com:80/foo/bar/baz', 'http://pkgs.host.com/bar/baz')).toBe( | ||
false, | ||
); | ||
|
||
npmRegistry.config = { | ||
'custom-host-suffix': 'pkgs.host.com', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Though, looking at this test a bit more now, I might have misunderstood. If someone is using a registry with a full path, e.g. But why not just specify the registry as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The This PR preserves the feature where the authorization token is only sent if it's applicable to that registry (which was one of the intentions of #3987). But I don't look at pathnames. Fix #3987 made sure that pathname and not just host is also checked when deciding if auth header should be used. But I'm not convinced that's needed. The reasoning is briefly described here: https://github.com/yarnpkg/website/pull/504/files by @lumaxis, but I need to understand that use case better. Are you around to discuss this @lumaxis? In particular how can I setup this VSTS registry to test yarn? I will dive deeper on this will get back here with my conclusions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey @KidkArolis, thanks for the ping!
My work in #3231 was really only picking up an existing solution proposed in #2507. Overall, I think my specific problem with VSTS would still be solved if the Does this help you at all? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the info! My conclusion is then that one could still use VSTS if the
or if using scopes
With config like that, authorization headers would be sent correctly. So it's good to know this would work for that VSTS case. Next, I'll look into how exactly |
||
}; | ||
const requestParams = mockRequestManager.request.mock.calls[0][0]; | ||
|
||
expect(npmRegistry.isRequestToRegistry('http://pkgs.host.com:80/foo/bar/baz', 'http://pkgs.host.com/bar/baz')).toBe( | ||
true, | ||
); | ||
expect(npmRegistry.isRequestToRegistry('http://pkgs.host.com:80/foo/bar/baz', '//pkgs.host.com/bar/baz')).toBe( | ||
true, | ||
); | ||
expect(requestParams.headers.authorization).toBe('Bearer testScopedAuthToken'); | ||
}); | ||
}); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,6 @@ import envReplace from '../util/env-replace.js'; | |
import Registry from './base-registry.js'; | ||
import {addSuffix} from '../util/misc'; | ||
import {getPosixPath, resolveWithHome} from '../util/path'; | ||
import normalizeUrl from 'normalize-url'; | ||
import {default as userHome, home} from '../util/user-home-dir'; | ||
import path from 'path'; | ||
import url from 'url'; | ||
|
@@ -99,32 +98,13 @@ export default class NpmRegistry extends Registry { | |
} | ||
} | ||
|
||
isRequestToRegistry(requestUrl: string, registryUrl: string): boolean { | ||
const normalizedRequestUrl = normalizeUrl(requestUrl); | ||
const normalizedRegistryUrl = normalizeUrl(registryUrl); | ||
const requestParsed = url.parse(normalizedRequestUrl); | ||
const registryParsed = url.parse(normalizedRegistryUrl); | ||
const requestHost = requestParsed.host || ''; | ||
const registryHost = registryParsed.host || ''; | ||
const requestPath = requestParsed.path || ''; | ||
const registryPath = registryParsed.path || ''; | ||
const customHostSuffix = this.getRegistryOrGlobalOption(registryUrl, 'custom-host-suffix'); | ||
|
||
return ( | ||
requestHost === registryHost && | ||
(requestPath.startsWith(registryPath) || | ||
// For some registries, the package path does not prefix with the registry path | ||
(typeof customHostSuffix === 'string' && requestHost.endsWith(customHostSuffix))) | ||
); | ||
} | ||
|
||
request(pathname: string, opts?: RegistryRequestOptions = {}, packageName: ?string): Promise<*> { | ||
// packageName needs to be escaped when if it is passed | ||
const packageIdent = (packageName && NpmRegistry.escapeName(packageName)) || pathname; | ||
const registry = this.getRegistry(packageIdent); | ||
const requestUrl = this.getRequestUrl(registry, pathname); | ||
|
||
const alwaysAuth = this.getRegistryOrGlobalOption(registry, 'always-auth'); | ||
const alwaysAuth = registry && this.getRegistryOrGlobalOption(registry, 'always-auth'); | ||
|
||
const headers = Object.assign( | ||
{ | ||
|
@@ -133,10 +113,8 @@ export default class NpmRegistry extends Registry { | |
opts.headers, | ||
); | ||
|
||
const isToRegistry = this.isRequestToRegistry(requestUrl, registry); | ||
|
||
// this.token must be checked to account for publish requests on non-scopped packages | ||
if (this.token || (isToRegistry && (alwaysAuth || this.isScopedPackage(packageIdent)))) { | ||
if (this.token || alwaysAuth || this.isScopedPackage(packageIdent)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The key change is basically: We stop checking if request isToRegistry (which is a strange and slightly complicated thing to understand, of course request is being made to a registry, what else would it be made to?) And instead we make use of In fact, we could push these 3 conditions ( |
||
const authorization = this.getAuth(packageIdent); | ||
if (authorization) { | ||
headers.authorization = authorization; | ||
|
@@ -271,6 +249,7 @@ export default class NpmRegistry extends Registry { | |
if (registry) { | ||
return String(registry); | ||
} | ||
return ''; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is to make sure that if URL is with a protocol, e.g. |
||
} | ||
|
||
for (const scope of [this.getScope(packageIdent), '']) { | ||
|
@@ -290,6 +269,11 @@ export default class NpmRegistry extends Registry { | |
} | ||
|
||
const baseRegistry = this.getRegistry(packageIdent); | ||
|
||
if (!baseRegistry) { | ||
return ''; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that we more strictly check what registry the request is for (e.g. don't conclude a request to |
||
|
||
const registries = [baseRegistry]; | ||
|
||
// If sending a request to the Yarn registry, we must also send it the auth token for the npm registry | ||
|
@@ -346,4 +330,15 @@ export default class NpmRegistry extends Registry { | |
getRegistryOrGlobalOption(registry: string, option: string): mixed { | ||
return this.getRegistryOption(registry, option) || this.getOption(option); | ||
} | ||
|
||
getAvailableRegistries(): Array<string> { | ||
const availableRegistries = super.getAvailableRegistries(); | ||
if (availableRegistries.indexOf(YARN_REGISTRY) === -1) { | ||
availableRegistries.push(YARN_REGISTRY); | ||
} | ||
if (availableRegistries.indexOf(DEFAULT_REGISTRY) === -1) { | ||
availableRegistries.push(DEFAULT_REGISTRY); | ||
} | ||
return availableRegistries; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In theory only |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By default,
npm login
populates the.npmrc
file with the full url, e.g.and not
I made sure this is not a regression by editing away the
https://
in my~/.npmrc
and runningnpm install
which gives menpm ERR! Only absolute URLs are supported