diff --git a/docs/config.rst b/docs/config.rst index 2de0ce0ec613..f3edfb178bbc 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -215,6 +215,15 @@ Those configuration options are documented below: onFailure Callback to be invoked upon a failed request. +.. describe:: allowSecretKey + + By default, Raven.js will throw an error if configured with a Sentry DSN that contains a secret key. + When using Raven.js with a web application accessed via a browser over the web, you should + only use your public DSN. But if you are using Raven.js in an environment like React Native or Electron, + where your application is running "natively" on a device and not accessed at a web address, you may need + to use your secret DSN string. To do so, set ``allowPrivateKey: true`` during configuration. + + Putting it all together ----------------------- diff --git a/src/raven.js b/src/raven.js index e99acc7aeb8e..bdece3d92d17 100644 --- a/src/raven.js +++ b/src/raven.js @@ -97,10 +97,6 @@ Raven.prototype = { } if (!dsn) return this; - var uri = this._parseDSN(dsn), - lastSlash = uri.path.lastIndexOf('/'), - path = uri.path.substr(1, lastSlash); - // merge in options if (options) { each(options, function(key, value){ @@ -113,6 +109,10 @@ Raven.prototype = { }); } + var uri = this._parseDSN(dsn), + lastSlash = uri.path.lastIndexOf('/'), + path = uri.path.substr(1, lastSlash); + this._dsn = dsn; // "Script error." is hard coded into browsers for errors that it can't read. @@ -127,6 +127,7 @@ Raven.prototype = { this._globalOptions.includePaths = joinRegExp(this._globalOptions.includePaths); this._globalKey = uri.user; + this._globalSecret = uri.pass && uri.pass.substr(1); this._globalProject = uri.path.substr(lastSlash + 1); this._globalServer = this._getGlobalServer(uri); @@ -725,8 +726,9 @@ Raven.prototype = { throw new RavenConfigError('Invalid DSN: ' + str); } - if (dsn.pass) - throw new RavenConfigError('Do not specify your private key in the DSN!'); + if (dsn.pass && !this._globalOptions.allowSecretKey) { + throw new RavenConfigError('Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key'); + } return dsn; }, @@ -990,14 +992,19 @@ Raven.prototype = { if (!this.isSetup()) return; + var auth = { + sentry_version: '7', + sentry_client: 'raven-js/' + this.VERSION, + sentry_key: this._globalKey + }; + if (this._globalSecret) { + auth.sentry_secret = this._globalSecret; + } + var url = this._globalEndpoint; (globalOptions.transport || this._makeRequest).call(this, { url: url, - auth: { - sentry_version: '7', - sentry_client: 'raven-js/' + this.VERSION, - sentry_key: this._globalKey - }, + auth: auth, data: data, options: globalOptions, onSuccess: function success() { diff --git a/test/raven.test.js b/test/raven.test.js index a1cc2eca1f7c..7db1576b62f8 100644 --- a/test/raven.test.js +++ b/test/raven.test.js @@ -1047,6 +1047,51 @@ describe('globals', function() { assert.isFunction(opts.onError); }); + it('should pass sentry_secret as part of auth params if specified', function () { + this.sinon.stub(Raven, 'isSetup').returns(true); + this.sinon.stub(Raven, '_makeRequest'); + this.sinon.stub(Raven, '_getHttpData').returns({ + url: 'http://localhost/?a=b', + headers: {'User-Agent': 'lolbrowser'} + }); + + Raven._globalEndpoint = 'http://localhost/store/'; + Raven._globalOptions = { + projectId: 2, + logger: 'javascript', + maxMessageLength: 100, + release: 'abc123' + };; + Raven._globalSecret = 'def'; // <-- secret + + Raven._send({message: 'bar'}); + var args = Raven._makeRequest.lastCall.args; + assert.equal(args.length, 1); + var opts = args[0]; + assert.equal(opts.url, 'http://localhost/store/'); + assert.deepEqual(opts.data, { + project: '2', + release: 'abc123', + logger: 'javascript', + platform: 'javascript', + request: { + url: 'http://localhost/?a=b', + headers: { + 'User-Agent': 'lolbrowser' + } + }, + event_id: 'abc123', + message: 'bar', + extra: {'session:duration': 100}, + }); + assert.deepEqual(opts.auth, { + sentry_client: 'raven-js/2.1.0', + sentry_key: 'abc', + sentry_secret: 'def', + sentry_version: '7' + }); + }); + it('should call globalOptions.transport if specified', function() { this.sinon.stub(Raven, 'isSetup').returns(true); this.sinon.stub(Raven, '_getHttpData').returns({ @@ -1528,12 +1573,33 @@ describe('Raven (public API)', function() { assert.equal(Raven, Raven.config(SENTRY_DSN, {foo: 'bar'}), 'it should return Raven'); assert.equal(Raven._globalKey, 'abc'); + assert.equal(Raven._globalSecret, ''); assert.equal(Raven._globalEndpoint, 'http://example.com:80/api/2/store/'); assert.equal(Raven._globalOptions.foo, 'bar'); assert.equal(Raven._globalProject, '2'); assert.isTrue(Raven.isSetup()); }); + it('throw an Error if the DSN contains a private/secret key', function () { + assert.throws(function () { + Raven.config('http://abc:def@example.com:80/2'); + }, Error); + }); + + it('will NOT throw an Error if the DSN contains a private/secret key AND allowSecretKey is true', function () { + assert.equal( + Raven, + Raven.config('http://abc:def@example.com:80/2', {allowSecretKey: true}), + 'it should return Raven' + ); + + assert.equal(Raven._globalKey, 'abc'); + assert.equal(Raven._globalSecret, 'def'); + assert.equal(Raven._globalEndpoint, 'http://example.com:80/api/2/store/'); + assert.equal(Raven._globalProject, '2'); + assert.isTrue(Raven.isSetup()); + }); + it('should work with a protocol relative DSN', function() { Raven.config('//abc@example.com/2');