forked from ging/oauth2-example-client
-
Notifications
You must be signed in to change notification settings - Fork 1
/
oauth2.js
171 lines (146 loc) · 5.51 KB
/
oauth2.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
var querystring= require('querystring'),
https= require('https'),
http= require('http'),
URL= require('url');
exports.OAuth2= function(clientId, clientSecret, baseSite, api_site, authorizePath, accessTokenPath, callbackURL, customHeaders) {
this._clientId= clientId;
this._clientSecret= clientSecret;
this._baseSite= baseSite;
this._authorizeUrl= authorizePath || "/oauth/authorize";
this._api_site = api_site;
this._accessTokenUrl= accessTokenPath || "/oauth/access_token";
this._callbackURL = callbackURL;
this._accessTokenName= "access_token";
this._authMethod= "Basic";
this._customHeaders = customHeaders || {};
}
// This 'hack' method is required for sites that don't use
// 'access_token' as the name of the access token (for requests).
// ( http://tools.ietf.org/html/draft-ietf-oauth-v2-16#section-7 )
// it isn't clear what the correct value should be atm, so allowing
// for specific (temporary?) override for now.
exports.OAuth2.prototype.setAccessTokenName= function ( name ) {
this._accessTokenName= name;
}
exports.OAuth2.prototype._getAccessTokenUrl= function() {
return this._api_site + this._accessTokenUrl;
}
// Build the authorization header. In particular, build the part after the colon.
// e.g. Authorization: Bearer <token> # Build "Bearer <token>"
exports.OAuth2.prototype.buildAuthHeader= function() {
var key = this._clientId + ':' + this._clientSecret;
var base64 = (new Buffer(key)).toString('base64');
return this._authMethod + ' ' + base64;
};
exports.OAuth2.prototype._request= function(method, url, headers, post_body, access_token, callback) {
var http_library= https;
var parsedUrl= URL.parse( url, true );
if( parsedUrl.protocol == "https:" && !parsedUrl.port ) {
parsedUrl.port= 443;
}
// As this is OAUth2, we *assume* https unless told explicitly otherwise.
if( parsedUrl.protocol != "https:" ) {
http_library= http;
}
var realHeaders= {};
for( var key in this._customHeaders ) {
realHeaders[key]= this._customHeaders[key];
}
if( headers ) {
for(var key in headers) {
realHeaders[key] = headers[key];
}
}
realHeaders['Host']= parsedUrl.host;
//realHeaders['Content-Length']= post_body ? Buffer.byteLength(post_body) : 0;
if( access_token && !('Authorization' in realHeaders)) {
if( ! parsedUrl.query ) parsedUrl.query= {};
parsedUrl.query[this._accessTokenName]= access_token;
}
var queryStr= querystring.stringify(parsedUrl.query);
if( queryStr ) queryStr= "?" + queryStr;
var options = {
host:parsedUrl.hostname,
port: parsedUrl.port,
path: parsedUrl.pathname + queryStr,
method: method,
headers: realHeaders
};
this._executeRequest( http_library, options, post_body, callback );
}
exports.OAuth2.prototype._executeRequest= function( http_library, options, post_body, callback ) {
// Some hosts *cough* google appear to close the connection early / send no content-length header
// allow this behaviour.
var allowEarlyClose= options.host && options.host.match(".*google(apis)?.com$");
var callbackCalled= false;
function passBackControl( response, result ) {
if(!callbackCalled) {
callbackCalled=true;
if( response.statusCode != 200 && (response.statusCode != 301) && (response.statusCode != 302) ) {
callback({ statusCode: response.statusCode, data: result });
} else {
callback(null, result, response);
}
}
}
var result= "";
var request = http_library.request(options, function (response) {
response.on("data", function (chunk) {
result+= chunk
});
response.on("close", function (err) {
if( allowEarlyClose ) {
passBackControl( response, result );
}
});
response.addListener("end", function () {
passBackControl( response, result );
});
});
request.on('error', function(e) {
callbackCalled= true;
callback(e);
});
if(options.method == 'POST' && post_body) {
request.write(post_body);
}
request.end();
}
exports.OAuth2.prototype.getAuthorizeUrl= function(response_type, scope) {
response_type = response_type || 'code';
var url = this._baseSite + this._authorizeUrl + '?response_type=' + response_type + '&client_id=' + this._clientId + '&state=xyz&redirect_uri=' + this._callbackURL;
if (scope) {
url += '&scope=' + scope;
}
return url;
}
exports.OAuth2.prototype.getOAuthAccessToken= function(code, callback) {
var post_data = 'grant_type=authorization_code&code=' + code + '&redirect_uri=' + this._callbackURL;
var post_headers= {
'Authorization': this.buildAuthHeader(),
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': post_data.length,
};
this._request("POST", this._getAccessTokenUrl(), post_headers, post_data, null, function(error, data, response) {
if( error ) callback(error);
else {
var results;
try {
// As of http://tools.ietf.org/html/draft-ietf-oauth-v2-07
// responses should be in JSON
results= JSON.parse(data);
}
catch(e) {
// .... However both Facebook + Github currently use rev05 of the spec
// and neither seem to specify a content-type correctly in their response headers :(
// clients of these services will suffer a *minor* performance cost of the exception
// being thrown
results= querystring.parse(data);
}
callback(null, results);
}
});
}
exports.OAuth2.prototype.get= function(url, access_token, callback) {
this._request("GET", url, {}, "", access_token, callback);
}