Skip to content

Commit c39e640

Browse files
authored
fix(core): Add sentry_client to auth headers (#5413)
This adds `sentry_client` to the auth headers* we send with every envelope request, as described [in the develop docs](https://develop.sentry.dev/sdk/overview/#authentication). In order to have access to the SDK metadata, the full `ClientOptions` object is now passed to `getEnvelopeEndpointWithUrlEncodedAuth`. Despite the fact that this is really an internal function, it's exported, so in order to keep everything backwards-compatible, for the moment it will also accept a string as the second argument, as it has in the past. Finally, all of our internal uses of the function have been switched to passing `options`, and there's a `TODO` in place so that we remember to remove the backwards compatibility in v8. Note that this change doesn't affect anyone using a tunnel, as no auth headers are sent in that case, in order to better cloak store requests from ad blockers. _\*The "headers" are actually querystring values, so as not to trigger CORS issues, but the effect is the same_ Fixes #5406
1 parent e1e5b49 commit c39e640

File tree

4 files changed

+59
-10
lines changed

4 files changed

+59
-10
lines changed

packages/browser/src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export class BrowserClient extends BaseClient<BrowserClientOptions> {
161161

162162
__DEBUG_BUILD__ && logger.log('Sending outcomes:', outcomes);
163163

164-
const url = getEnvelopeEndpointWithUrlEncodedAuth(this._dsn, this._options.tunnel);
164+
const url = getEnvelopeEndpointWithUrlEncodedAuth(this._dsn, this._options);
165165
const envelope = createClientReportEnvelope(outcomes, this._options.tunnel && dsnToString(this._dsn));
166166

167167
try {

packages/core/src/api.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DsnComponents, DsnLike } from '@sentry/types';
1+
import { ClientOptions, DsnComponents, DsnLike, SdkInfo } from '@sentry/types';
22
import { dsnToString, makeDsn, urlEncode } from '@sentry/utils';
33

44
const SENTRY_API_VERSION = '7';
@@ -16,12 +16,13 @@ function _getIngestEndpoint(dsn: DsnComponents): string {
1616
}
1717

1818
/** Returns a URL-encoded string with auth config suitable for a query string. */
19-
function _encodedAuth(dsn: DsnComponents): string {
19+
function _encodedAuth(dsn: DsnComponents, sdkInfo: SdkInfo | undefined): string {
2020
return urlEncode({
2121
// We send only the minimum set of required information. See
2222
// https://github.com/getsentry/sentry-javascript/issues/2572.
2323
sentry_key: dsn.publicKey,
2424
sentry_version: SENTRY_API_VERSION,
25+
...(sdkInfo && { sentry_client: `${sdkInfo.name}/${sdkInfo.version}` }),
2526
});
2627
}
2728

@@ -30,8 +31,21 @@ function _encodedAuth(dsn: DsnComponents): string {
3031
*
3132
* Sending auth as part of the query string and not as custom HTTP headers avoids CORS preflight requests.
3233
*/
33-
export function getEnvelopeEndpointWithUrlEncodedAuth(dsn: DsnComponents, tunnel?: string): string {
34-
return tunnel ? tunnel : `${_getIngestEndpoint(dsn)}?${_encodedAuth(dsn)}`;
34+
export function getEnvelopeEndpointWithUrlEncodedAuth(
35+
dsn: DsnComponents,
36+
// TODO (v8): Remove `tunnelOrOptions` in favor of `options`, and use the substitute code below
37+
// options: ClientOptions = {} as ClientOptions,
38+
tunnelOrOptions: string | ClientOptions = {} as ClientOptions,
39+
): string {
40+
// TODO (v8): Use this code instead
41+
// const { tunnel, _metadata = {} } = options;
42+
// return tunnel ? tunnel : `${_getIngestEndpoint(dsn)}?${_encodedAuth(dsn, _metadata.sdk)}`;
43+
44+
const tunnel = typeof tunnelOrOptions === 'string' ? tunnelOrOptions : tunnelOrOptions.tunnel;
45+
const sdkInfo =
46+
typeof tunnelOrOptions === 'string' || !tunnelOrOptions._metadata ? undefined : tunnelOrOptions._metadata.sdk;
47+
48+
return tunnel ? tunnel : `${_getIngestEndpoint(dsn)}?${_encodedAuth(dsn, sdkInfo)}`;
3549
}
3650

3751
/** Returns the url to the report dialog endpoint. */

packages/core/src/baseclient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
104104
this._options = options;
105105
if (options.dsn) {
106106
this._dsn = makeDsn(options.dsn);
107-
const url = getEnvelopeEndpointWithUrlEncodedAuth(this._dsn, options.tunnel);
107+
const url = getEnvelopeEndpointWithUrlEncodedAuth(this._dsn, options);
108108
this._transport = options.transport({
109109
recordDroppedEvent: this.recordDroppedEvent.bind(this),
110110
...options.transportOptions,

packages/core/test/lib/api.test.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,55 @@
11
/* eslint-disable deprecation/deprecation */
2+
import { ClientOptions, DsnComponents } from '@sentry/types';
23
import { makeDsn } from '@sentry/utils';
34

45
import { getEnvelopeEndpointWithUrlEncodedAuth, getReportDialogEndpoint } from '../../src/api';
56

67
const ingestDsn = 'https://[email protected]:1234/subpath/123';
78
const dsnPublic = 'https://[email protected]:1234/subpath/123';
89
const tunnel = 'https://hello.com/world';
10+
const _metadata = { sdk: { name: 'sentry.javascript.browser', version: '12.31.12' } } as ClientOptions['_metadata'];
911

1012
const dsnPublicComponents = makeDsn(dsnPublic);
1113

1214
describe('API', () => {
13-
test('getEnvelopeEndpoint', () => {
14-
expect(getEnvelopeEndpointWithUrlEncodedAuth(dsnPublicComponents)).toEqual(
15-
'https://sentry.io:1234/subpath/api/123/envelope/?sentry_key=abc&sentry_version=7',
15+
describe('getEnvelopeEndpointWithUrlEncodedAuth', () => {
16+
it.each([
17+
[
18+
"doesn't include `sentry_client` when called with only DSN",
19+
dsnPublicComponents,
20+
undefined,
21+
'https://sentry.io:1234/subpath/api/123/envelope/?sentry_key=abc&sentry_version=7',
22+
],
23+
['uses `tunnel` value when called with `tunnel` as string', dsnPublicComponents, tunnel, tunnel],
24+
[
25+
'uses `tunnel` value when called with `tunnel` in options',
26+
dsnPublicComponents,
27+
{ tunnel } as ClientOptions,
28+
tunnel,
29+
],
30+
[
31+
'uses `tunnel` value when called with `tunnel` and `_metadata` in options',
32+
dsnPublicComponents,
33+
{ tunnel, _metadata } as ClientOptions,
34+
tunnel,
35+
],
36+
[
37+
'includes `sentry_client` when called with `_metadata` in options and no tunnel',
38+
dsnPublicComponents,
39+
{ _metadata } as ClientOptions,
40+
'https://sentry.io:1234/subpath/api/123/envelope/?sentry_key=abc&sentry_version=7&sentry_client=sentry.javascript.browser%2F12.31.12',
41+
],
42+
])(
43+
'%s',
44+
(
45+
_testName: string,
46+
dsnComponents: DsnComponents,
47+
tunnelOrOptions: string | ClientOptions | undefined,
48+
expected: string,
49+
) => {
50+
expect(getEnvelopeEndpointWithUrlEncodedAuth(dsnComponents, tunnelOrOptions)).toBe(expected);
51+
},
1652
);
17-
expect(getEnvelopeEndpointWithUrlEncodedAuth(dsnPublicComponents, tunnel)).toEqual(tunnel);
1853
});
1954

2055
describe('getReportDialogEndpoint', () => {

0 commit comments

Comments
 (0)