UNPKG

8.47 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 VError = require('verror').VError;
12var WError = require('verror').WError;
13var async = require('async');
14var request = require('request');
15var _ = require('lodash');
16
17module.exports = function mountFriendRequest(antisocialApp) {
18
19 var router = antisocialApp.router;
20 var config = antisocialApp.config;
21 var db = antisocialApp.db;
22 var authUserMiddleware = antisocialApp.authUserMiddleware;
23
24 var testRegex = /^\/([a-zA-Z0-9\-.]+)\/friend-request$/;
25
26 debug('mounting GET /username/friend-request', testRegex);
27
28 router.post(testRegex, function handleFriendRequest(req, res) {
29 var matches = req.path.match(testRegex);
30 var username = matches[1];
31
32 if (!req.body.remoteEndPoint) {
33 debug('remoteEndPoint not supplied');
34 return res.status(400).send('remoteEndPoint not supplied');
35 }
36
37 var requestToken = req.body.requestToken;
38 var invite = req.body.inviteToken;
39
40 if (!req.body.remoteEndPoint.match(/(^|\s)((https?:\/\/)?[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?)/gi)) {
41 debug('remoteEndPoint not a valid url');
42 return res.status(400).send('remoteEndPoint not a valid url');
43 }
44
45 var remoteEndPoint = url.parse(req.body.remoteEndPoint);
46
47 async.waterfall([
48 function getUser(cb) {
49 debug('/friend-request getUser');
50 db.getInstances('users', [{
51 'property': 'username',
52 'value': username
53 }], function (err, userInstances) {
54 if (err) {
55 return cb(new VError(err, 'user not found'));
56 }
57
58 if (userInstances.length > 1) {
59 return cb(new VError('more than one user matching username'));
60 }
61
62 if (!userInstances.length) {
63 return cb(new VError('user not found'));
64 }
65
66 var user = userInstances[0];
67
68 var myEndPoint = config.publicHost + config.APIPrefix + '/' + user.username;
69
70 if (myEndPoint === req.body.remoteEndPoint) {
71 return cb(new VError(err, 'can not friend yourself'));
72 }
73
74 cb(err, user);
75 });
76 },
77 function checkBlocked(user, cb) {
78 db.getInstances('blocks', [{
79 'property': 'userId',
80 'value': user.id
81 }, {
82 'property': 'remoteEndPoint',
83 'value': req.body.remoteEndPoint
84 }], function (err, blockedInstances) {
85 if (err) {
86 return cb(new VError(err, 'error reading blocks'));
87 }
88
89 if (blockedInstances.length) {
90 return cb(new VError(err, 'blocked'));
91 }
92
93 cb(null, user);
94 });
95 },
96 function checkDupeFriend(user, cb) {
97 debug('/friend-request checkDupeFriend');
98 db.getInstances('friends', [{
99 'property': 'userId',
100 'value': user.id
101 }, {
102 'property': 'remoteEndPoint',
103 'value': req.body.remoteEndPoint
104 }], function (err, friendInstances) {
105 if (err) {
106 return cb(new VError(err, 'error reading friends'));
107 }
108
109 if (friendInstances.length) {
110 return cb(new VError(err, 'duplicate friend request'));
111 }
112
113 cb(err, user);
114 });
115 },
116 function keyPair(user, cb) {
117 debug('/friend-request keyPair');
118 encryption.getKeyPair(function (err, pair) {
119 if (err) {
120 var e = new VError(err, '/friend-request keyPair failed');
121 return cb(e);
122 }
123 cb(null, user, pair);
124 });
125 },
126 function processInvite(user, pair, cb) {
127 if (!invite) {
128 return async.setImmediate(function () {
129 cb(null, user, pair, null);
130 });
131 }
132
133 db.getInstances('invitations', [{
134 'property': 'userId',
135 'value': user.id
136 }, {
137 'property': 'token',
138 'value': invite
139 }], function (err, invitations) {
140 if (err) {
141 var e = new VError(err, '/friend-request processInvite failed reading invitations');
142 return cb(e);
143 }
144
145 if (invitations.length > 1) {
146 return cb(new VError('/friend-request processInvite more than one user invitation token'));
147 }
148
149 if (!invitations.length) {
150 return cb(new VError('/friend-request processInvite invitation not found'));
151 }
152
153 cb(null, user, pair, invitations[0]);
154 });
155 },
156 function createPendingFriend(user, pair, invitation, cb) {
157 debug('/friend-request createPendingFriend');
158
159 var newFriend = {
160 'status': 'pending',
161 'remoteRequestToken': requestToken,
162 'remoteEndPoint': req.body.remoteEndPoint,
163 'remoteHost': remoteEndPoint.protocol + '//' + remoteEndPoint.host,
164 'localRequestToken': uuid(),
165 'localAccessToken': uuid(),
166 'keys': pair,
167 'audiences': ['public'],
168 'hash': crc.crc32(req.body.remoteEndPoint).toString(16),
169 'userId': user.id,
170 'inviteToken': invite
171 };
172
173 db.newInstance('friends', newFriend, function (err, friendInstance) {
174 if (err) {
175 var e = new VError(err, '/friend-request createPendingFriend failed');
176 return cb(e);
177 }
178 cb(null, user, friendInstance, invitation);
179 });
180 },
181 function exchangeToken(user, friend, invitation, cb) {
182
183 var myEndPoint = config.publicHost + config.APIPrefix + '/' + user.username;
184 var payload = {
185 'endpoint': myEndPoint,
186 'requestToken': requestToken
187 };
188
189 var options = {
190 'url': fixIfBehindProxy(friend.remoteEndPoint + '/exchange-token'),
191 'form': payload,
192 'json': true
193 };
194
195 debug('/friend-request exchangeToken POST ', options);
196
197 request.post(options, function (err, response, body) {
198 if (err) {
199 var e = new VError(err, '/friend-request exchangeToken request error');
200 return cb(e, user, friend);
201 }
202 if (response.statusCode !== 200) {
203 var e = new VError(err, '/friend-request exchangeToken got http status: ' + response.statusCode);
204 return cb(e, user, friend);
205 }
206
207 if (!_.has(body, 'status') || !_.has(body, 'accessToken') || !_.has(body, 'publicKey')) {
208 e = new VError(err, '/friend-request exchangeToken got unexpected response %j', body);
209 return cb(e, user, friend);
210 }
211
212 debug('/friend-request exchangeToken got ', body);
213
214 cb(null, user, friend, invitation, body);
215 });
216 },
217 function saveToken(user, friend, invitation, exchange, cb) {
218 db.getInstances('friends', [{
219 'property': 'userId',
220 'value': user.id
221 }], function (err, friends) {
222 if (err) {
223 var e = new VError(err, '/friend-request createPendingFriend failed reading friends');
224 return cb(e, user, friend);
225 }
226
227 var unique = 0;
228 for (var i = 0; i < friends.length; i++) {
229 var friend = friends[i];
230 if (friend.remoteUsername === exchange.username) {
231 ++unique;
232 }
233 }
234
235 var update = {
236 'remoteAccessToken': exchange.accessToken,
237 'remotePublicKey': exchange.publicKey,
238 'remoteName': exchange.name,
239 'remoteUsername': exchange.username,
240 'uniqueRemoteUsername': unique ? exchange.username + '-' + unique : exchange.username
241 };
242
243 if (invitation) {
244 update.status = 'accepted';
245 update.audiences = ['public', 'friends'];
246 }
247
248 db.updateInstance('friends', friend.id, update, function (err, friend) {
249 if (err) {
250 var e = new VError(err, '/friend-request saveToken error');
251 return cb(e, user, friend);
252 }
253
254 cb(null, user, friend);
255 });
256 });
257 }
258 ], function (err, user, friend) {
259 if (err) {
260 var e = new WError(err, 'request-friend failed');
261 debug('friend-request error', e.cause().message);
262 res.send({
263 'status': 'error',
264 'reason': e.message,
265 'details': e.cause().message
266 });
267
268 if (friend) {
269 db.deleteInstance('friends', friend.id, function (err) {
270 if (err) {
271 console.log('/friend-request error deleting pending friend', err);
272 }
273 });
274 }
275 }
276 else {
277 if (friend.inviteToken) {
278 antisocialApp.emit('new-friend', {
279 'info': {
280 'friend': friend,
281 'user': user
282 }
283 });
284 }
285 else {
286 antisocialApp.emit('new-friend-request', {
287 'info': {
288 'friend': friend,
289 'user': user
290 }
291 });
292 }
293
294 // if success hand a request token back to caller
295 res.send({
296 'status': 'ok',
297 'requestToken': friend.localRequestToken,
298 });
299 }
300 });
301 });
302};