-
-
Notifications
You must be signed in to change notification settings - Fork 33
/
Copy pathparse-facebook-user-session.js
188 lines (157 loc) · 5.69 KB
/
parse-facebook-user-session.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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
var moment = require('moment');
var querystring = require('querystring');
/**
* @name parseFacebookUserSession
* @class
*
* <p>A middleware module for logging in a Parse.User using Facebook in express.
* For more information, see <a href="https://parse.com/docs/hosting_guide#webapp-facebook">our
* hosting guide</a>.</p>
*
* <p>Params includes the following:<pre>
* clientId (required): The client id of the Facebook App.
* appSecret (required): The app secret of the Facebook App.
* scope (optional): What type of access you need. A comma separated list of scopes.
* verbose (optional): If true, output debug messages to console.log.
* redirectUri (optional): The path on this server to use for handling the
* redirect callback from Facebook. If this is omitted, it defaults to
* /login.</pre></p>
*/
var parseFacebookUserSession = function(params) {
params = params || {};
if (!params.clientId || !params.appSecret) {
throw "You must specify a Facebook clientId and appSecret.";
}
/**
* Logs using console.log if verbose is passed in when configuring the
* middleware.
*/
var maybeLog = function() {
if (params.verbose) {
console.log.apply(console, arguments);
}
};
var relativeRedirectUri = params.redirectUri || "/login";
/**
* Returns the absolute url of the redirect path for this request.
*/
var getAbsoluteRedirectUri = function(req) {
return req.protocol + "://" + req.headers.host + req.app.path() + relativeRedirectUri;
};
/**
* Starts the Facebook login OAuth process.
*/
var beginLogin = function(req, res) {
maybeLog("Starting Facebook login...");
Parse.Promise.as().then(function() {
// Make a request object. Its objectId will be our XSRF token.
maybeLog("Creating ParseFacebookTokenRequest...");
var request = new Parse.Object("ParseFacebookTokenRequest");
return request.save({
url: req.url.slice(1),
ACL: new Parse.ACL()
});
}).then(function(request) {
maybeLog("Redirecting for Facebook OAuth.");
// Put the XSRF token into a cookie so that we can match it later.
res.cookie("requestId", request.id);
// Redirect the user to start the Facebook OAuth flow.
var url = 'https://www.facebook.com/dialog/oauth?';
url = url + querystring.stringify({
client_id: params.clientId,
redirect_uri: getAbsoluteRedirectUri(req),
state: request.id,
scope: params.scope
});
res.redirect(302, url);
});
};
/**
* Handles the last stage of the Facebook login OAuth redirect.
*/
var endLogin = function(req, res) {
maybeLog("Handling request callback for Facebook login...");
if (req.query.state !== req.cookies.requestId) {
maybeLog("Request failed XSRF validation.");
res.send(500, "Bad Request");
return;
}
var url = 'https://graph.facebook.com/oauth/access_token?';
url = url + querystring.stringify({
client_id: params.clientId,
redirect_uri: getAbsoluteRedirectUri(req),
client_secret: params.appSecret,
code: req.query.code
});
var accessToken = null;
var expires = null;
var facebookData = null;
Parse.Promise.as().then(function() {
maybeLog("Fetching access token...");
return Parse.Cloud.httpRequest({ url: url });
}).then(function(response) {
maybeLog("Fetching user data from Facebook...");
var data = querystring.parse(response.text);
accessToken = data.access_token;
expires = data.expires;
var url = 'https://graph.facebook.com/me?';
url = url + querystring.stringify({
access_token: accessToken
});
return Parse.Cloud.httpRequest({ url: url });
}).then(function(response) {
maybeLog("Logging into Parse with Facebook token...");
facebookData = response.data;
var expiration = moment().add('seconds', expires).format(
"YYYY-MM-DDTHH:mm:ss.SSS\\Z");
return Parse.FacebookUtils.logIn({
id: response.data.id,
access_token: accessToken,
expiration_date: expiration
});
}).then(function(response) {
maybeLog("Becoming Parse user...");
return Parse.User.become(response.getSessionToken());
}).then(function(user) {
maybeLog("Saving Facebook data for user...");
user.set("name", facebookData.name);
return user.save();
}).then(function(user) {
maybeLog("Fetching ParseFacebookTokenRequest for " +
req.query.state + "...");
var request = new Parse.Object("ParseFacebookTokenRequest");
request.id = req.query.state;
return request.fetch({ useMasterKey: true });
}).then(function(request) {
maybeLog("Deleting used ParseFacebookTokenRequest...");
// Let's delete this request so that no one can reuse the token.
var url = request.get("url");
return request.destroy({ useMasterKey: true }).then(function() {
return url;
});
}).then(function(url) {
maybeLog("Success!");
res.redirect(302, url);
}, function(error) {
maybeLog("Failed! " + JSON.stringify(error));
res.send(500, error);
});
};
/**
* The actual middleware method.
*/
return function(req, res, next) {
// If the user is already logged in, there's nothing to do.
if (Parse.User.current()) {
return next();
}
// If this is the Facebook login redirect URL, and a code is present, then handle the code.
// If a code is not present, begin the login process.
if (req.path === relativeRedirectUri && req.query.code) {
endLogin(req, res);
} else {
beginLogin(req, res);
}
};
};
module.exports = parseFacebookUserSession;