Skip to content

Commit ae9f81f

Browse files
committed
auto convert .p12 to .pem so user doesn't have to
1 parent 3ab8467 commit ae9f81f

File tree

10 files changed

+72
-51
lines changed

10 files changed

+72
-51
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
node_modules
22

33
# below contain secrets
4-
*.pem
4+
*.p12
55
options.json

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ node_js:
44
before_install:
55
- openssl aes-256-cbc
66
-k $encrypt_file_password
7-
-in test/fixtures/gaServiceAcctKeyDev.pem.enc
8-
-out test/fixtures/gaServiceAcctKeyDev.pem
7+
-in test/fixtures/gaServiceAcctKeyDev.p12.enc
8+
-out test/fixtures/gaServiceAcctKeyDev.p12
99
-d
1010
- openssl aes-256-cbc
1111
-k $encrypt_file_password

README.md

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,27 @@ npm install --save ga-extractor
2525

2626
## Usage
2727

28-
Sign up for a GA [Service Account](https://developers.google.com/accounts/docs/OAuth2ServiceAccount), download your `.pem` private key.
28+
Sign up for a GA [Service Account](https://developers.google.com/accounts/docs/OAuth2ServiceAccount), download your `.p12` private key.
2929

3030
```js
3131
var gaExtractor = require('ga-extractor');
3232

3333
var options = {
34-
profileId: "xxxxxxxx", // optional, define once here or define a different one in every queryObj
3534
clientEmail: "[email protected]",
36-
keyPath: "test/fixtures/xxx.pem",
37-
keyContent: "Bag Attributes...", // need either keyPath or keyContent
35+
// need either keyPath or keyContent
36+
keyPath: "test/fixtures/xxx.p12",
37+
keyContent: "Bag Attributes...",
3838
// below are optional
39+
profileId: "xxxxxxxx", // define once here or define a different one in every queryObj
3940
impersonatedUser: "[email protected]"
4041
proxy: "http://proxy.example.com"
4142
};
4243

4344
var gaExtractor = new GaExtractor(options);
4445

46+
// To build your query, see
47+
// https://developers.google.com/analytics/devguides/reporting/core/v3/reference#q_summary
48+
// and https://developers.google.com/analytics/devguides/reporting/core/v3/reference
4549
var queryObj = {
4650
'start-date': '31daysAgo',
4751
'end-date': 'yesterday',
@@ -50,16 +54,19 @@ var queryObj = {
5054
// no need to define 'max-results', always extracts all data
5155
};
5256

53-
// no need to call .auth, extraction only happen after successful auth
5457
gaExtractor.extract(queryObj)
5558
.then(function (data) {
5659
// do something with data returned, e.g. transform & load into database
57-
})
58-
.catch(function (err) {console.error(err)})
60+
data = [
61+
["United States", "5471"],
62+
["United Kingdom", "1084"],
63+
["France", "801"]
64+
// ...
65+
];
66+
})
67+
.catch(function (err) {console.error(err)});
5968
```
6069

61-
To construct your `queryObj`, see [here](https://developers.google.com/analytics/devguides/reporting/core/v3/reference#q_summary) and [here](https://developers.google.com/analytics/devguides/reporting/core/v3/reference).
62-
6370
To try your query without writing any code, use the [Query Explorer Tool](https://ga-dev-tools.appspot.com/explorer/).
6471

6572
Example of original data returned by GA API:
@@ -102,22 +109,16 @@ Example of original data returned by GA API:
102109
}
103110
```
104111

105-
`.extract` returns only what's in the `rows` object above:
106-
```json
107-
[
108-
["United States", "5471"],
109-
["United Kingdom", "1084"],
110-
["France", "801"]
111-
]
112-
```
112+
`.extract` returns only the content of `rows` object above.
113+
113114

114115
## Contribution
115116

116117
Install dependencies: `npm install`
117118

118119
Ensure tests pass: `npm test`
119120

120-
TODO: all tests are integration tests and go against live GA API server. Docs on fixtures needed for testing coming soon. In the mean time you can submit a PR and owner will manually test.
121+
TODO: all tests are integration tests and execute against live GA API server. Docs on fixtures needed for testing coming soon. In the mean time you can submit a PR and owner will manually test.
121122

122123
Build .js for distribution: `npm build`
123124

lib/index.js

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
(function() {
2-
var GaExtractor, Promise, RateLimiter, gaMaxRowsPerRequest, gaRateLimiter, p;
2+
var GaExtractor, Promise, RateLimiter, gaMaxRowsPerRequest, gaRateLimiter, gp12, p;
33

44
p = require('path');
55

@@ -9,6 +9,8 @@
99

1010
gaRateLimiter = Promise.promisifyAll(new RateLimiter(1, 500));
1111

12+
gp12 = Promise.promisify(require('google-p12-pem'));
13+
1214
gaMaxRowsPerRequest = 10000;
1315

1416
GaExtractor = (function() {
@@ -29,22 +31,33 @@
2931
},
3032
proxy: this.config.proxy
3133
}).data.ga);
32-
this.authClient = new this.gApi.auth.JWT(this.config.clientEmail, this.config.keyPath, this.config.keyContent, "https://www.googleapis.com/auth/analytics.readonly", this.impersonatedUser);
3334
return;
3435
}
3536

3637
GaExtractor.prototype.auth = function() {
3738
return new Promise((function(_this) {
3839
return function(resolve, reject) {
39-
_this.authClient.authorize(function(err, token) {
40-
if (err) {
41-
reject(new Error("OAuth error; err = " + err.error));
40+
var _convertKey;
41+
_convertKey = new Promise(function(_resolve) {
42+
if (_this.config.keyPath) {
43+
return _resolve(gp12(_this.config.keyPath));
4244
} else {
43-
resolve(token);
45+
return _resolve(null);
4446
}
4547
});
46-
_this.gApi.options({
47-
auth: _this.authClient
48+
return _convertKey.then(function(keyContent) {
49+
var _authClient;
50+
_authClient = new _this.gApi.auth.JWT(_this.config.clientEmail, null, keyContent, "https://www.googleapis.com/auth/analytics.readonly", _this.impersonatedUser);
51+
_authClient.authorize(function(err, token) {
52+
if (err) {
53+
return reject(new Error("OAuth error; err = " + err.error));
54+
} else {
55+
return resolve(token);
56+
}
57+
});
58+
return _this.gApi.options({
59+
auth: _authClient
60+
});
4861
});
4962
};
5063
})(this));

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"homepage": "https://github.com/rayshan/ga-extractor",
3535
"dependencies": {
3636
"bluebird": "~2.3",
37+
"google-p12-pem": "0.0.1",
3738
"googleapis": "~1.0",
3839
"limiter": "~1.0.5"
3940
},
@@ -43,7 +44,7 @@
4344
"coffee-script": "~1.8",
4445
"gulp": "~3.8",
4546
"gulp-coffee": "^2.2.0",
46-
"gulp-mocha": "~1.1",
47+
"gulp-mocha": "^2.x",
4748
"mocha": "~2.0"
4849
},
4950
"license": "MIT"

src/index.coffee

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ p = require 'path'
33
Promise = require 'bluebird'
44
RateLimiter = require('limiter').RateLimiter
55
# promisify for better control flow
6+
# limit # of concurrent requests to 1 in 1/2 sec to not hammer GA server
67
gaRateLimiter = Promise.promisifyAll new RateLimiter 1, 500
8+
gp12 = Promise.promisify require 'google-p12-pem'
79

810
# ==========
911

@@ -24,34 +26,38 @@ class GaExtractor
2426
proxy: @config.proxy
2527
}).data.ga
2628

27-
# define auth obj; needed for initial auth & extractions
28-
@authClient = new @gApi.auth.JWT(
29-
@config.clientEmail,
30-
@config.keyPath, # key as .pem file
31-
@config.keyContent,
32-
"https://www.googleapis.com/auth/analytics.readonly", # scope uri
33-
@impersonatedUser
34-
)
3529
return
3630

3731
auth: -> new Promise (resolve, reject) =>
38-
@authClient.authorize (err, token) ->
39-
# returns expiry_date: 1406182540 (16 days) and refresh_token: 'jwt-placeholder'
40-
if err
41-
reject new Error "OAuth error; err = #{ err.error }"
32+
# convert .p12 key to .pem
33+
_convertKey = new Promise (_resolve) =>
34+
if @config.keyPath
35+
_resolve gp12 @config.keyPath
4236
else
43-
resolve token
44-
return
45-
@gApi.options auth: @authClient
46-
return
37+
_resolve null
38+
39+
_convertKey.then (keyContent) =>
40+
_authClient = new @gApi.auth.JWT(
41+
@config.clientEmail
42+
null
43+
keyContent
44+
"https://www.googleapis.com/auth/analytics.readonly" # scope uri
45+
@impersonatedUser
46+
)
47+
48+
_authClient.authorize (err, token) =>
49+
# returns .expiry_date in 1 hr
50+
if err
51+
reject new Error "OAuth error; err = #{ err.error }"
52+
else
53+
resolve token
54+
@gApi.options auth: _authClient
4755

4856
extract: (queryObj) ->
49-
# limit # of concurrent requests to not hammer GA server
5057
@auth().delay 500
5158
.bind @
5259
.then gaRateLimiter.removeTokensAsync 1
53-
# .get(0) - for some reason data[1] is whole object returned by request again
54-
.then -> @ga.getAsync(queryObj).get 0
60+
.then -> @ga.getAsync(queryObj).get 0 # [1] is whole object returned by request again
5561
.then (results) ->
5662
data = results.rows
5763
return data unless results.totalResults > gaMaxRowsPerRequest
1.72 KB
Binary file not shown.
-1.03 KB
Binary file not shown.

test/fixtures/options.json.enc

0 Bytes
Binary file not shown.

test/test.coffee

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ describe 'GaExtractor', ->
3838
clientEmail: options.clientEmail
3939
}).should.throw Error
4040

41-
it 'should instantiate with a .pem key file', ->
41+
it 'should instantiate with a .p12 key file', ->
4242
(-> new GaExtractor {
4343
profileId: options.profileId
4444
clientEmail: options.clientEmail
4545
keyPath: options.keyPath
4646
}).should.not.throw Error
4747

48-
it 'should instantiate with keyContent as string read from .pem file', ->
48+
it 'should instantiate with keyContent as string read from .p12 file', ->
4949
(-> new GaExtractor {
5050
profileId: options.profileId
5151
clientEmail: options.clientEmail

0 commit comments

Comments
 (0)