UNPKG

7.5 kBJavaScriptView Raw
1// Copyright Michael Rhodes. 2017,2018. All Rights Reserved.
2// This file is licensed under the MIT License.
3// License text available at https://opensource.org/licenses/MIT
4
5var uuid = require('uuid');
6var crc = require('crc');
7var encryption = require('antisocial-encryption');
8var fixIfBehindProxy = require('../lib/utilities').fixIfBehindProxy;
9var url = require('url');
10var debug = require('debug')('antisocial-friends');
11var async = require('async');
12var request = require('request');
13var VError = require('verror').VError;
14var WError = require('verror').WError;
15var _ = require('lodash');
16
17
18module.exports = function mountRequestFriend(antisocialApp) {
19
20 var router = antisocialApp.router;
21 var config = antisocialApp.config;
22 var db = antisocialApp.db;
23 var authUserMiddleware = antisocialApp.authUserMiddleware;
24
25 var testRegex = /^\/([a-zA-Z0-9\-.]+)\/request-friend$/;
26
27 debug('mounting GET /username/request-friend', testRegex);
28
29 router.get(testRegex, authUserMiddleware, function (req, res) {
30 var matches = req.path.match(testRegex);
31 var user = matches[1];
32
33 // must be a logged in user
34 var currentUser = req.antisocialUser;
35 if (!currentUser) {
36 debug('not logged in');
37 return res.sendStatus(401);
38 }
39
40 // by convention expects user to match username
41 if (currentUser.username !== user) {
42 debug('username mismatch');
43 return res.sendStatus(400);
44 }
45
46 var myEndPoint = config.publicHost + config.APIPrefix + '/' + currentUser.username;
47
48 // endpoint must be supplied and it must be a valid url
49 var endpoint = req.query.endpoint;
50 if (!endpoint) {
51 debug('endpoint not supplied');
52 return res.status(400).send('endpoint not supplied');
53 }
54
55 if (!endpoint.match(/(^|\s)((https?:\/\/)?[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?)/gi)) {
56 debug('endpoint not a valid url');
57 return res.status(400).send('endpoint not a valid url');
58 }
59
60 if (myEndPoint === req.query.endpoint) {
61 debug('can not friend yourself');
62 return res.status(400).send('can not friend yourself');
63 }
64
65 var remoteEndPoint = url.parse(req.query.endpoint);
66 var invite = req.query.invite;
67
68 async.waterfall([
69 function checkDupeFriend(cb) {
70 debug('/request-friend checkDupeFriend');
71 db.getInstances('friends', [{
72 'property': 'userId',
73 'value': currentUser.id
74 }, {
75 'property': 'remoteEndPoint',
76 'value': req.query.endpoint
77 }], function (err, friendInstances) {
78 if (err) {
79 return cb(new VError(err, 'error reading friends'));
80 }
81
82 if (friendInstances.length) {
83 return cb(new VError(err, 'duplicate friend request'));
84 }
85
86 cb();
87 });
88 },
89 function keyPair(cb) {
90 debug('/request-friend keyPair');
91 encryption.getKeyPair(function (err, pair) {
92 if (err) {
93 var e = new VError(err, '/request-friend keyPair failed');
94 return cb(e);
95 }
96 cb(null, pair);
97 });
98 },
99 function createPendingFriend(pair, cb) {
100 debug('/request-friend createPendingFriend');
101
102 var newFriend = {
103 'originator': true,
104 'status': 'pending',
105 'remoteEndPoint': req.query.endpoint,
106 'remoteHost': remoteEndPoint.protocol + '//' + remoteEndPoint.host,
107 'localRequestToken': uuid(),
108 'localAccessToken': uuid(),
109 'keys': pair,
110 'audiences': ['public'],
111 'hash': crc.crc32(req.query.endpoint).toString(16),
112 'userId': currentUser.id,
113 'inviteToken': invite
114 };
115
116 db.newInstance('friends', newFriend, function (err, friendInstance) {
117 if (err) {
118 var e = new VError(err, '/request-friend createPendingFriend failed');
119 return cb(e);
120 }
121 cb(null, friendInstance);
122 });
123 },
124 function makeFriendRequest(friend, cb) {
125
126 var payload = {
127 'remoteEndPoint': myEndPoint,
128 'requestToken': friend.localRequestToken,
129 'inviteToken': invite
130 };
131
132 var options = {
133 'url': fixIfBehindProxy(friend.remoteEndPoint + '/friend-request'),
134 'form': payload,
135 'json': true,
136 'timeout': 10000
137 };
138
139 debug('/request-friend makeFriendRequest POST', options);
140
141 request.post(options, function (err, response, body) {
142 var e;
143
144 if (err) {
145 e = new VError(err, '/request-friend makeFriendRequest failed');
146 return cb(e, friend);
147 }
148
149 if (response.statusCode !== 200) {
150 e = new VError('/request-friend makeFriendRequest failed (http status: ' + response.statusCode + ' ' + body + ')');
151 return cb(e, friend);
152 }
153
154 if (_.get(body, 'status') !== 'ok') {
155 e = new VError('/request-friend makeFriendRequest failed (reason: ' + _.get(body, 'details') + ')');
156 return cb(e, friend);
157 }
158
159 debug('/request-friend makeFriendRequest got ', body);
160
161 cb(err, friend, body.requestToken);
162 });
163 },
164 function exchangeToken(friend, requestToken, cb) {
165
166 var payload = {
167 'endpoint': myEndPoint,
168 'requestToken': requestToken
169 };
170
171 var options = {
172 'url': fixIfBehindProxy(friend.remoteEndPoint + '/exchange-token'),
173 'form': payload,
174 'json': true
175 };
176
177 debug('/request-friend exchangeToken POST ', options);
178
179 request.post(options, function (err, response, body) {
180 if (err) {
181 var e = new VError(err, '/request-friend exchangeToken request error');
182 return cb(e, friend);
183 }
184 if (response.statusCode !== 200) {
185 var e = new VError(err, '/request-friend exchangeToken got http status: ' + response.statusCode);
186 return cb(e, friend);
187 }
188
189 if (!_.has(body, 'status') || !_.has(body, 'accessToken') || !_.has(body, 'publicKey')) {
190 e = new VError(err, '/request-friend exchangeToken got unexpected response %j', body);
191 return cb(e, friend);
192 }
193
194 debug('/request-friend exchangeToken got ', body);
195
196 cb(null, friend, body);
197 });
198 },
199 function saveToken(friend, exchange, cb) {
200
201 db.getInstances('friends', [{
202 'property': 'userId',
203 'value': currentUser.id
204 }], function (err, friends) {
205 if (err) {
206 var e = new VError(err, '/request-friend saveToken failed reading friends');
207 return cb(e, friend);
208 }
209
210 var unique = 0;
211 for (var i = 0; i < friends.length; i++) {
212 var friend = friends[i];
213 if (friend.remoteUsername === exchange.username) {
214 ++unique;
215 }
216 }
217
218 var update = {
219 'remoteAccessToken': exchange.accessToken,
220 'remotePublicKey': exchange.publicKey,
221 'remoteName': exchange.name,
222 'remoteUsername': exchange.username,
223 'uniqueRemoteUsername': unique ? exchange.username + '-' + unique : exchange.username
224 };
225
226 if (invite && exchange.status === 'accepted') {
227 update.status = 'accepted';
228 update.audiences = ['public', 'friends'];
229 }
230
231 db.updateInstance('friends', friend.id, update, function (err, friend) {
232 if (err) {
233 var e = new VError(err, '/request-friend saveToken error');
234 return cb(e, friend);
235 }
236
237 cb(null, friend);
238 });
239 });
240 }
241 ], function (err, friend) {
242 if (err) {
243
244 var e = new WError(err, 'request-friend failed');
245
246 res.send({
247 'status': 'error',
248 'reason': e.message,
249 'details': e.cause().message
250 });
251
252 if (friend) {
253 db.deleteInstance('friends', friend.id, function (err) {
254 if (err) {
255 console.log('/request-friend error deleting pending friend', err);
256 }
257 });
258 }
259 }
260 else {
261
262 // if success hand a request token back to caller
263 res.send({
264 'status': 'ok'
265 });
266 }
267 });
268 });
269};