UNPKG

29.9 kBMarkdownView Raw
1<img src="https://github.com/antiSocialNet/antiSocial/raw/master/assets/octocloud/logo.jpg" height="200">
2
3# antiSocial
4
5Building blocks for myAntiSocial.net
6
7## antisocial-friends
8
9This module mounts routes for any expressjs application wishing to support building and maintaining antisocial 'friend' relationships whether on the same server/application and/or across distributed servers and applications. The protocol generates key pairs unique to the friend relationship and exchanges public keys for later use in exchanging user to user encrypted messages over socket.io connections.
10
11```
12var antisocial = require('antisocial-friends');
13var antisocialApp = antisocial(app, config, dbAdaptor, getAuthenticatedUser, listener);
14```
15
16### Parameters:
17
18**app** is the express application
19
20**config** is a javascript object with the following properties
21
22```
23var config = {
24 'APIPrefix': '/antisocial', // where to mount the routes
25 'publicHost': 'http://127.0.0.1:3000', // public protocol and host
26 'port': 3000 // port this service is listening on (only used if behind a load balancer or reverse proxy)
27}
28```
29
30**dbAdaptor** is an abstract data store that will be called when the app needs to
31store or retrieve data from the database. For a simple example implementation
32see app.js for a simple memory store implementation of the methods and a loopback
33application adaptor below. Yours should implement the required methods to work
34within your environment (mysql, mongo etc.)
35
36**getAuthenticatedUser** is an express middleware function that gets the current
37logged in user and exposes it on req.antisocialUser. This is application specific but typically would be a token cookie that can be used to look up a user. See simple example in app.js
38
39**listener** is an express http(s) listener for setting up socket.io listeners
40
41This function returns an **antisocialApp** object which an EventEmitter.
42
43## User Endpoints
44
45Endpoints are URLS that function as the base address of a user on an antisocial aware server.
46
47'https://some.antisocial.server/api-prefix/local-username'
48
49The URL form of the endpoints conform to the following conventions:
50
51`api-prefix` is the location where antisocial-friends is mounted defined in config
52
53`local-username` is a unique username for the local user
54
55### On the requestor's server
56The following API endpoints are mounted
57
58#### make a friend request
59```
60GET /api-prefix/local-username/request-friend
61 Query params:
62 endpoint: endpoint url of friend to connect with
63 invite: an invite token if this is in response to an invitation from the user
64```
65
66#### cancel a pending friend request
67```
68POST /api-prefix/local-username/request-friend-cancel
69 Request Body: (application/x-www-form-urlencoded)
70 endpoint: endpoint of the pending request
71```
72
73### On the requestee's server
74The following API endpoints are mounted
75
76#### accept a pending friend request
77```
78/api-prefix/local-username/friend-request-accept
79 Request Body: (application/x-www-form-urlencoded)
80 endpoint: endpoint of the pending request
81```
82
83#### decline a pending friend request
84```
85/api-prefix/local-username/friend-request-decline
86 Request Body: (application/x-www-form-urlencoded)
87 endpoint: endpoint of the pending request
88```
89
90### either side can update or delete the relationship once accepted
91```
92POST /api-prefix/local-username/friend-update
93 Request Body: (application/x-www-form-urlencoded)
94 endpoint: endpoint of the friend
95 status: 'delete'|'block'
96 audiences: array of audiences for the friend eg. ["public","friends","some custom audience"]
97```
98
99## socket.io feeds
100
101Accepted friends establish socket.io connections to update each other about activity. Posts, photos, IM etc are sent to the friend or groups of friends in audiences. The details of the messages are application specific but the mechanism for sending and responding to messages is driven by `data` and `backfill` events received by the application.
102
103## Events
104You can handle the following events as needed.
105
106### new-friend-request: a new friend request received
107Relevant details are in user (a user instance) and friend (a friend instance). Typically would be used to notify the user of the pending friend request.
108```
109antisocialApp.on('new-friend-request', function (user, friend) {
110 console.log('antisocial new-friend-request %s %j', user.username, friend.remoteEndPoint);
111});
112```
113
114### new-friend: a new friend
115Relevant details are in user (a user instance) and friend (a friend instance). Typically would be used to notify the user that their requests has been approved and the user's friends that they have a new friend.
116```
117antisocialApp.on('new-friend', function (user, friend) {
118 console.log('antisocial new-friend %s %j', user.username, friend.remoteEndPoint);
119});
120```
121
122### friend-updated: the relationship has changed
123Relevant details are in user (a user instance) and friend (a friend instance). The friend might have changed the audiences for the user. Typically the user would remove any cache of activity originating with the friend and re-load by requesting emitting a highwater event.
124```
125antisocialApp.on('friend-updated', function (user, friend) {
126 console.log('antisocial friend-updated %s %s', user.username, friend.remoteEndPoint);
127});
128```
129
130### friend-deleted: either user or the friend has deleted the other
131Typically user would clean up the database and remove any activity about or by the friend.
132```
133antisocialApp.on('friend-deleted', function (user, friend) {
134 console.log('antisocial friend-deleted %s %s', user.username, friend.remoteEndPoint);
135});
136```
137
138### open-activity-connection event: a friend activity feed has been connected.
139Typically would hook up any process that would create activity messages to be transmitted to friends. Could also be used to send a 'highwater' event to the friend to request activity since last logged in.
140```
141antisocialApp.on('open-activity-connection', function (user, friend, emitter) {
142 console.log('antisocial open-activity-connection %s<-%s', user.username, friend.remoteEndPoint);
143 emitter('post', 'highwater', highwater);
144});
145```
146
147### user can send data to a friend using emitter function specifying an appId
148The appId indicates the class or type of message we are sending (eg. post, reply, photo, IM)
149
150Parameters:
151 `appId` is used to direct messages to the appropriate listeners (see activity-data-xxx event)
152 `eventType` 'data' or 'highwater'
153 `message object` JSON object to transmit
154
155```
156emitter(appId, eventType, {application specific message});
157```
158
159### activity-data-xxx event: xxx is the appId defined in the emitter call
160Friend has sent user a message. Application would typically keep track of highwater mark of last message seen so the user can request a backfill from the friend of activity since the user was last connected.
161```
162antisocialApp.on('activity-data-appId', function (user, friend, data) {
163 console.log('antisocial activity-data-post user: %s friend: %s data: %j', user.name, friend.remoteEndPoint, data);
164});
165```
166
167### activity-backfill-xxx event: xxx is the appId
168Typically would send any activity that has happened since the last message received by the friend (highwater). This could be a timestamp or a record number, it's up to the application to define this behavior
169
170```
171antisocialApp.on('activity-backfill-appId', function (user, friend, highwater, emitter) {
172 console.log('antisocial activity-backfill-post user: %s friend: %s highwater: %s', user.name, friend.remoteEndPoint, highwater);
173
174 // send posts from requested highwater to end of posts
175 emitter('post', 'data', {application specific message});
176});
177```
178
179### close-activity-connection: activity feed closed.
180Typically used to clean up any event handlers set up in open-activity-connection.
181```
182antisocialApp.on('close-activity-connection', function (user, friend, reason) {
183 console.log('antisocial close-activity-connection %s<-%s %s', user.username, friend.remoteEndpoint, reason);
184});
185```
186
187### open-notification-connection event: The user has opened the notification feed.
188A user has subscribed to notifications using browser or app.
189```
190antisocialApp.on('open-notification-connection', function (user, emitter) {
191 console.log('antisocial open-notification-connection %s', user.username);
192});
193```
194
195### notification-data event:
196User has sent server a message.
197```
198antisocialApp.on('notification-data', function (user, friend, data) {
199 console.log('antisocial notification-data user: %s friend: %s data: %j', user.name, friend.remoteEndPoint, data);
200});
201```
202
203### notification-backfill-xxx event: xxx is the appId defined in the emit
204Typically would send any notifications that has happened since the last notification was received by the user (highwater). This could be a timestamp or a record number, it's up to the application to define this behavior.
205
206```
207antisocialApp.on('notification-backfill-appId', function (user, highwater, emitter) {
208 console.log('antisocial notification-backfill-post user: %s highwater: %s', user.name, highwater);
209
210 // send posts from requested highwater to end of posts
211 emitter('post', 'data', {application specific message});
212});
213```
214
215### close-notification-connection event
216```
217antisocialApp.on('close-notification-connection', function (user, reason) {
218 console.log('antisocial close-notification-connection %s %s', user.username, reason);
219});
220```
221
222#### establish activity feed connection
223Once connected this will emit an 'open-notification-connection' event on the antisocalApp so the host application can set up its protocol for exchanging messages.
224```
225antisocialApp.activityFeed.connect(user, friend);
226```
227
228## The data structures maintained by these protocols
229This app uses the following data collections:
230
231* users: username property is required to build urls
232* friends: several properties maintained by the antisocial protocol
233* invitations: use to simplify "be my friend" invitations
234* blocks: list of blocked friends
235
236friends, invitations and blocks are related to users by a foreign key `userId`
237which is set to the id of the appropriate user when created.
238
239The schema definition is implementation specific and up to the implementor.
240The following is an example db adaptor for a Loopback.io application. dbHandlers
241must support all the methods in this example.
242```
243function dbHandler() {
244 var self = this;
245
246 self.models = {
247 'users': 'MyUser',
248 'friends': 'Friend',
249 'invitations': 'Invite',
250 'blocks': 'Block'
251 };
252
253 // store an item
254 this.newInstance = function (collectionName, data, cb) {
255 server.models[self.models[collectionName]].create(data, function (err, instance) {
256 if (cb) {
257 cb(err, instance);
258 }
259 else {
260 return instance;
261 }
262 });
263 };
264
265 // get an item by matching some properties.
266 // pairs are a list of property/value pairs that are anded
267 // when querying the database example:
268 // [
269 // { 'property':'userId', 'value': 1 },
270 // { 'property':'localAccessToken', 'value': 'jhgasdfjhgsdfjhg' }
271 // ]
272 this.getInstances = function (collectionName, pairs, cb) {
273 var query = {
274 'where': {
275 'and': []
276 }
277 };
278
279 for (var i = 0; i < pairs.length; i++) {
280 var prop = pairs[i].property;
281 var value = pairs[i].value;
282 var pair = {};
283 pair[prop] = value;
284 query.where.and.push(pair);
285 }
286
287 server.models[self.models[collectionName]].find(query, function (err, found) {
288 if (cb) {
289 cb(err, found);
290 }
291 else {
292 return found;
293 }
294 });
295 };
296
297 // update item properties by id
298 this.updateInstance = function (collectionName, id, patch, cb) {
299 server.models[self.models[collectionName]].findById(id, function (err, instance) {
300 if (err) {
301 return cb(new Error('error reading ' + collectionName));
302 }
303 if (!instance) {
304 return cb(new Error('error ' + collectionName + ' id ' + id + ' not found'));
305 }
306
307 instance.updateAttributes(patch, function (err, updated) {
308 if (err) {
309 return cb(new Error('error updating ' + collectionName));
310 }
311 if (cb) {
312 cb(null, updated);
313 }
314 else {
315 return updated;
316 }
317 });
318 });
319 };
320
321 this.deleteInstance = function (collectionName, id, cb) {
322 server.models[self.models[collectionName]].destroyById(id, function (err) {
323 if (cb) {
324 cb(err);
325 }
326 });
327 };
328}
329
330db = new dbHandler();
331```
332
333### User properties
334The user is application specific but we expect the following properties
335```
336{
337 "name": "user one",
338 "username": "user-one",
339 "id": "c5503436-634c-461f-9d90-ba9e438516c1"
340}
341```
342
343### Friend Invitation properties
344```
345{
346 "token": "testinvite",
347 "userId": "53757323-8555-4ef5-b66c-a58351ce6181",
348 "id": "6165e25e-2c31-47d3-a8c2-7c75737d4003"
349}
350```
351### Block list properties
352```
353{
354 "remoteEndPoint": "http://127.0.0.1:3000/antisocial/user-three",
355 "userId": "53757323-8555-4ef5-b66c-a58351ce6181",
356 "id": "7281bcd3-94f8-4906-9714-89d7e5b5c349"
357}
358```
359
360### Friend Properties
361The result of a friend request and a friend accept is 2 friends records, one owned by the requestor and one by the requestee (who could be on different servers). The requestor's is marked as 'originator'. The structure contains exchanged key pairs that can be used to communicate securely.
362
363```
364[
365 {
366 "originator": true,
367 "status": "accepted",
368 "remoteEndPoint": "http://127.0.0.1:3000/antisocial/user-two",
369 "remoteHost": "http://127.0.0.1:3000",
370 "localRequestToken": "cbf875ad-5eb7-43e4-8028-415ddf6d95a9",
371 "localAccessToken": "fbb8d3be-b199-45a6-b46e-3bcbbfedd0aa",
372 "keys": {
373 "public": "-----BEGIN PUBLIC KEY-----\r\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3BotZWYZu/rtFqTOHpdzM9+b2m7c\r\n7I2CtkZrnJ5zPQzmXDg9gLWqImAFrqcR3Ee3LMLniuKsFSYz2I/ERmXiXzv2e8wr14AeuXOV\r\nFzqcsDypKrbtT88lZLor6bt0kQOP7pFcesOedocoU9/DpnRkOYeI9MHsZN1pyZVvzLfkHvdL\r\n08ktiWwjNoFV8EL2h13sVZIFt/GaoPrv/SeWzb9oyAGAcp671smBsExCsafgwXAKYBQHIzrI\r\nScxNBzP2d9/z1ZKQ/dtXbGvWheZ0Ci1G6ngYSdx8APBIRFK+hhRdnhhpbat5juWoMs2dTG8q\r\nWIu45ntjfg7BHLRLExRE6un5lwIDAQAB\r\n-----END PUBLIC KEY-----\r\n",
374 "private": "-----BEGIN RSA PRIVATE KEY-----\r\nMIIEowIBAAKCAQEA3BotZWYZu/rtFqTOHpdzM9+b2m7c7I2CtkZrnJ5zPQzmXDg9gLWqImAF\r\nrqcR3Ee3LMLniuKsFSYz2I/ERmXiXzv2e8wr14AeuXOVFzqcsDypKrbtT88lZLor6bt0kQOP\r\n7pFcesOedocoU9/DpnRkOYeI9MHsZN1pyZVvzLfkHvdL08ktiWwjNoFV8EL2h13sVZIFt/Ga\r\noPrv/SeWzb9oyAGAcp671smBsExCsafgwXAKYBQHIzrIScxNBzP2d9/z1ZKQ/dtXbGvWheZ0\r\nCi1G6ngYSdx8APBIRFK+hhRdnhhpbat5juWoMs2dTG8qWIu45ntjfg7BHLRLExRE6un5lwID\r\nAQABAoIBAEUvFUXiKgSkgxGzC/chs9yCVQL8BgV1FbkluX2pcJ+oBmDGbM6gS7IybJbRfRO4\r\nlyNCwHUvetfLAlD4H8HhFJ7Kwld3ffBnHUE9y4dZrRbYenQqu71yZ1aaDmORwLo0XHGoz2Dn\r\nTFAFe++hTmZr/3T13V7R9fRehHoQtuuqgdIZZAoshX90JpIhJ9Px6F1scgWgmBRH3XcsCbxb\r\nOMjABrVFINv5YUANRwUAwC2DYUBEuptRnEtm4X3++Afg97hK2brR9ofgpw44ej7JhovHeZti\r\n1Xm0AhqP/T6GQa55MS0rPryq5bbIjr/SBqJr4VkAmJJMx+P8KOa8gfBPefj9cgECgYEA+4Bz\r\ntT86OY/Pa+QJTSXZsskCqVlCQbj1V3CAt5dwXXir9NEwlE6yrc2jReqq07bDdqeadQUEwTaZ\r\nyioXxFozRNhs0rQPBoKLB0HuFqOm8GULcB0m3ScWJec5Rz9TCQQrMdUDQaZTLCXQs9izcVmd\r\nhBT1SLLZ3zjJt7hKVwafkqECgYEA4An08IkbEjwfcq7zOHNzh5Dm4G9jRW62vx5WFL+xktl6\r\nDg14IXMH7TVg2Gl96U40JZDBQW2bLH6pcGLp5UAj9ZqtoVW9BN+LZ3uY25hCEhYfAE0zVhhc\r\nE0VLkdyKp7wSQFicqAfPjryMsFTIgCrywo1BxxMibe773ai26YL32TcCgYEA2MOkdrGxGE2X\r\no9DeJ20ZDdvr/FPfJFAqvRtNBW9zvEw2QQJPkXOm0t/q+mbAp0rdexYHrRYPPAw4TqMq6uQn\r\nTg4O9SeVz7GR7EZp039ncchVLGMjzPZUQ4TfvEWa5ql+JSwH63xUMTfCgk+ikW6AsYdyxR7J\r\nY3hJe5xODmW6ASECgYBpPuQs9wublljjpCIn+7xjDAQZnNoSrP72a0be+mpt5PI8lcFAXWx0\r\n16WGJJB8wDspBoZyuQ2zalEotZ7RDj+WSjKU3tUr6+PuGhbl2fH30yJ/HsUmBc2DVAM7I1KT\r\nl3svdTEqknjDwfmJgFqsMwDVukwTO/7pi+IP8Aj1S4wpIwKBgDJwDd0eNPhMRf1wi+rfTefL\r\nb1lo/sSw0/vgyH2GKLnY+svw7p2k2GoPvbT7AEkUFDxh3HYbdKPx2hUO0rbkM94FyA8eSUU4\r\n+osMYDv8bIMwve7dfmRKl7vN2TWhkFhjREwPdFf6xbCV91BstV/qcCrOJJiXYmCrQODZ2qHJ\r\nvvOf\r\n-----END RSA PRIVATE KEY-----\r\n"
375 },
376 "audiences": ["public", "friends"],
377 "hash": "f08bf78",
378 "userId": "7ca18fd6-5d23-4930-8b64-3812f3a8f012",
379 "inviteToken": "testinvite",
380 "id": "97c46a56-c323-4941-bf61-e2c959748617",
381 "remoteAccessToken": "aabaa39b-cb60-4f29-948f-8483bc29f21c",
382 "remotePublicKey": "-----BEGIN PUBLIC KEY-----\r\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAihLKPC8s1fIi4IR33n8iGA+VInab\r\niN0PzqiI7iPPJlfApwPIrxIVD+SvQXlMOOQNsh8sOADaiIH4w8FvG+3WvFrD/fkOcYl7Uk1t\r\nO5iMxC0McYU0b1zfplivqP9obaSkAWJkv3M2IRIpqJHuCJV/Gx3THFBdgTSqtSqrIJSG3Kjr\r\nNi7xHQPimi5LL9CZJFNbNmGyDly2WIWQM1k6EMgrIn6hR9OaElyAjx88YhJwFIDRS+dGNC2+\r\nu4rcK5YdLuezZGff84rPFWyZueMmEK16xb1P3fhDwFTU2KtmqCs47p7eaz2Mlw1ek1E9nlP+\r\nkmPuhWGF9pIUVbEg9co4IFgnFwIDAQAB\r\n-----END PUBLIC KEY-----\r\n",
383 "remoteName": "user two",
384 "remoteUsername": "user-two",
385 "uniqueRemoteUsername": "user-two"
386 },
387 {
388 "status": "accepted",
389 "remoteRequestToken": "cbf875ad-5eb7-43e4-8028-415ddf6d95a9",
390 "remoteEndPoint": "http://127.0.0.1:3000/antisocial/user-one",
391 "remoteHost": "http://127.0.0.1:3000",
392 "localRequestToken": "97e2a0b7-7a0d-46cb-8be6-4253b842067a",
393 "localAccessToken": "aabaa39b-cb60-4f29-948f-8483bc29f21c",
394 "keys": {
395 "public": "-----BEGIN PUBLIC KEY-----\r\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAihLKPC8s1fIi4IR33n8iGA+VInab\r\niN0PzqiI7iPPJlfApwPIrxIVD+SvQXlMOOQNsh8sOADaiIH4w8FvG+3WvFrD/fkOcYl7Uk1t\r\nO5iMxC0McYU0b1zfplivqP9obaSkAWJkv3M2IRIpqJHuCJV/Gx3THFBdgTSqtSqrIJSG3Kjr\r\nNi7xHQPimi5LL9CZJFNbNmGyDly2WIWQM1k6EMgrIn6hR9OaElyAjx88YhJwFIDRS+dGNC2+\r\nu4rcK5YdLuezZGff84rPFWyZueMmEK16xb1P3fhDwFTU2KtmqCs47p7eaz2Mlw1ek1E9nlP+\r\nkmPuhWGF9pIUVbEg9co4IFgnFwIDAQAB\r\n-----END PUBLIC KEY-----\r\n",
396 "private": "-----BEGIN RSA PRIVATE KEY-----\r\nMIIEowIBAAKCAQEAihLKPC8s1fIi4IR33n8iGA+VInabiN0PzqiI7iPPJlfApwPIrxIVD+Sv\r\nQXlMOOQNsh8sOADaiIH4w8FvG+3WvFrD/fkOcYl7Uk1tO5iMxC0McYU0b1zfplivqP9obaSk\r\nAWJkv3M2IRIpqJHuCJV/Gx3THFBdgTSqtSqrIJSG3KjrNi7xHQPimi5LL9CZJFNbNmGyDly2\r\nWIWQM1k6EMgrIn6hR9OaElyAjx88YhJwFIDRS+dGNC2+u4rcK5YdLuezZGff84rPFWyZueMm\r\nEK16xb1P3fhDwFTU2KtmqCs47p7eaz2Mlw1ek1E9nlP+kmPuhWGF9pIUVbEg9co4IFgnFwID\r\nAQABAoIBAD9wortEcbVbq+q88taoU2H6xusu1AfuinTJuyCwE13qs/oJIwxNop/K0zuiIAOD\r\nxUcyS37v5XkTPtmy5vpOLXwduC/ZX2mLYb5PFQFs9kCs8iq2qYEBi0FDPnLH55N5MmHwc5oD\r\ntbs8PSfW5SfMiLpM2dMIme3j5QuYr0go9k4sHcravEZWewc89ARRgvC5KnZ3Xo5bOBgq4C2W\r\nSEZF+5LzwG1pcNoNil5JFUXWmV9Kxzrv0N7KaaACxzkiACNL07viW/u5uZo2/f49OZ9gq2qk\r\nd5M5pwGC/uI3J5c7ipDq+Hlf1nzpn6AxwpRraUeLziF5+RG/3dgrNiCeQ6Ll3AECgYEA6yKZ\r\n/Tsj9TZXQeg/mQJ6PlYRqMqdnk6+rRu+37D0B7IfcE0nXKAH+sjWR66qwKm21MEj03pvr9+5\r\nHbE6YuKjq7B0UHBrGf0hyXxd3xMyaLbAmzunHUnmVcMAnavA0pTnubg7DsrL40TNHGiWZcoS\r\ntKd7zb4qnJ0bvHRuUA4u4qcCgYEAllNOKKauIV8C4HYKRgfDqWTJMTUbzva1RNKVRS8CpLkm\r\ntkY4Fskgy5dTq++zDNELfs5O6PGNj7jkLpkFi3XJrvLhIVkLH0wc5y8kfw/Btkf7mTo/PnG4\r\nw1M8P7/+YVj8+wnb/1JgQLt19aEU8dqjM38nmjHvXE/EVfxsvM4RVhECgYEApa+IGqxlthBI\r\nhCSHS+Y3BV3Yq7u6PSb3rTtz0GP8UL/u708ugVIyzUBf3bryjzgHoPtHp2kK8j8PTiDoJ23U\r\nLtLz4wqULYf1GukLrHj2eFrudXQfWcANEjmKYY/5G2nZr0BmPRIhgU+lyHLaJ3ewnqO11VA+\r\n7oS2WqEgakDUQNkCgYBGXO/0rzBKhoJ+NkJQzUmUfIx/7+/4TBpFAJzGKV7/Y3rvTqbqY3Jq\r\nWYbcr/ILSb4ruL3O42HzqAOGnDGwOY4RybX/OgKuv523yKU4pFNz0vW9nzoDLI/jPY6x+FhF\r\nkLW5e7/yHsjXA+gO9Tssib5iWF5dGoqDlwK7jNAJABu1QQKBgAOVsey9v/+GxLprhVuVIMjb\r\n+2fhh0fUfEtx9G1DplhW88AwAFHTSRfffT7VqJBSqcYHajnXDKz9mn3pC1ASyO0QnZxAy0YR\r\nyT7gsd4gOgUuk7IqOCLPNXg4TlTOTV9wsJyNQ6s+EbZqyIAhUzhr4ESEZ/qQLVpMKZ4eLDrp\r\ndx2J\r\n-----END RSA PRIVATE KEY-----\r\n"
397 },
398 "audiences": ["public", "friends"],
399 "hash": "64aeb3ef",
400 "userId": "700acf91-4ee1-4aba-8e18-136e0cc33560",
401 "inviteToken": "testinvite",
402 "id": "771cec97-d8ab-4ae9-b3d4-7c29521a4732",
403 "remoteAccessToken": "fbb8d3be-b199-45a6-b46e-3bcbbfedd0aa",
404 "remotePublicKey": "-----BEGIN PUBLIC KEY-----\r\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3BotZWYZu/rtFqTOHpdzM9+b2m7c\r\n7I2CtkZrnJ5zPQzmXDg9gLWqImAFrqcR3Ee3LMLniuKsFSYz2I/ERmXiXzv2e8wr14AeuXOV\r\nFzqcsDypKrbtT88lZLor6bt0kQOP7pFcesOedocoU9/DpnRkOYeI9MHsZN1pyZVvzLfkHvdL\r\n08ktiWwjNoFV8EL2h13sVZIFt/GaoPrv/SeWzb9oyAGAcp671smBsExCsafgwXAKYBQHIzrI\r\nScxNBzP2d9/z1ZKQ/dtXbGvWheZ0Ci1G6ngYSdx8APBIRFK+hhRdnhhpbat5juWoMs2dTG8q\r\nWIu45ntjfg7BHLRLExRE6un5lwIDAQAB\r\n-----END PUBLIC KEY-----\r\n",
405 "remoteName": "user one",
406 "remoteUsername": "user-one",
407 "uniqueRemoteUsername": "user-one"
408 }
409]
410```
411
412## AntiSocial Friend Protocol
413
414protocol for making a friend request
415------------------------------------
416```
417requester sets up pending Friend data on requester's server (/request-friend)
418 requester calls requestee with a requestToken
419 requestee sets up pending Friend data on requestee's server (/friend-request)
420 requestee calls requester to exchange the requestToken for an accessToken and publicKey (/friend-exchange-token)
421 requestee returns requestToken to requester
422 requestee triggers 'new-friend-request' event for requestee application
423 requester calls requestee to exchange requestToken for accessToken and publicKey (/friend-exchange-token)
424```
425
426protocol for accepting a friend request
427---------------------------------------
428```
429requestee marks requester as accepted and grants access to 'public' and 'friends' (/friend-request-accept)
430 requestee calls requester to update status (/friend-webhook action=friend-request-accepted)
431 requester marks requestee as accepted and grants access to 'public' and 'friends'
432 trigger a 'new-friend' event for requestor application
433 trigger a 'new-friend' event for requestee application
434```
435
436At the end of this protocol both users will have a Friend record holding the accessToken and the public key of the friend. With these credentials they can exchange signed encrypted messages for notifying each other of activity in their accounts.
437
438`accessToken` is a uuid that is used to authenticate connection requests, `requestToken` is a uuid which is used to retrieve an accessToken
439
440
441### Friend Request
442---
443
444Use case: Michael wants to friend Alan and already knows the address of Alan's server antisocial endpoint.
445`http://emtage.com/antisocial/ae`
446
447Michael logs on to his account on his server.
448
449Michael enters Alan's address on the friend request form.
450
451Alan's public profile information is displayed to confirm that the address is correct.
452
453Michael clicks the 'add friend' button which starts the friend request protocol.
454
455### FRIEND REQUEST
456---
457
4581. Michael's server creates a 'Friend' record in his database marking it as 'pending' and setting the flag 'originator' to indicate that Michael is making the request. This record has a unique 'requestToken', 'accessToken' and an RSA key pair. These credentials will be exchanged with Alan's server.
459```
460Michael's Browser Michael's server Alan's server Alan's Browser
461----------------- ---------------- ---------------- ----------------
462
463GET --------------------->
464http://rhodes.com/antisocial/mr/request-friend?endpoint=http://emtage.com/antisocial/ae
465```
466
4672. Michael's server sends a POST request to Alan's server to initiate the friend request.
468```
469Michael's Browser Michael's server Alan's server Alan's Browser
470----------------- ---------------- ---------------- ----------------
471
472 POST -------------------->
473 http://emtage.com/antisocial/ae/friend-request
474 BODY {
475 'remoteEndPoint': 'http://rhodes.com/antisocial/mr',
476 'requestToken': Michaels Request Token
477 }
478```
4793. Alans's server connects to Michael's server to validate the origin of the request and to exchange Michael's requestToken for an accessToken.
480```
481Michael's Browser Michael's server Alan's server Alan's Browser
482----------------- ---------------- ---------------- ----------------
483
484 <------------------------ POST
485 http://rhodes.com/antisocial/mr/friend-exchange
486 BODY {
487 'endpoint': 'http://emtage.com/antisocial/ae',
488 'requestToken': Michaels Request Token
489 }
490```
4914. Michael's server looks up the friend record and returns access credentials to Alan's server
492```
493Michael's Browser Michael's server Alan's server Alan's Browser
494----------------- ---------------- ---------------- ----------------
495
496 RESPONSE ---------------->
497 {
498 'status': 'ok',
499 'accessToken': Michael's Access Token,
500 'publicKey': Michael's public key
501 }
502```
5035. Alan's server creates a 'Friend' record in his database marking it as 'pending' saving Michael's accessToken and the publicKey and notifies Alan of the pending request. Alan's server returns his requestToken to Michael's server so Micael's server can complete the exchange of credentials.
504```
505Michael's Browser Michael's server Alan's server Alan's Browser
506----------------- ---------------- ---------------- ----------------
507
508 <------------------------ RESPONSE
509 {
510 'status': 'ok',
511 'requestToken': Alan's RequestToken
512 }
513```
5146. Michael's server connects to Alan's server to exchange Alan's request token for an accessToken
515```
516Michael's Browser Michael's server Alan's server Alan's Browser
517----------------- ---------------- ---------------- ----------------
518
519 POST ------------------->
520 http://emtage.com/antisocial/ae/friend-exchange
521 BODY {
522 'endpoint': http://rhodes.com/antisocial/mr,
523 'requestToken': Alan's Request Token
524 }
525```
5267. Alan's server looks up the friend record by the requestToken and returns access credentials to Michael's server
527```
528Michael's Browser Michael's server Alan's server Alan's Browser
529----------------- ---------------- ---------------- ----------------
530
531 <------------------------ RESPONSE
532 {
533 'status': 'ok',
534 'accessToken': Alan's AccessToken,
535 'publicKey': Alan's public key
536 }
537```
538
5398. Michael's server saves Alan's accessToken and the publicKey in the pending Friend record and returns status to the client.
540```
541Michael's Browser Michael's server Alan's server Alan's Browser
542----------------- ---------------- ---------------- ----------------
543
544<------------------------ RESPONSE
545 { 'status':'ok' }
546```
547
548### FRIEND ACCEPT
549---
550
5511. Alan accepts friend Michael's request by clicking the button in the UI calling the accept-friend endpoint
552```
553Michael's Browser Michael's server Alan's server Alan's Browser
554----------------- ---------------- ---------------- ----------------
555
556 <----------------------- POST
557 http://emtage.com/antisocial/ae/friend-request-accept
558
559 BODY { 'endpoint': http://rhodes.com/antisocial/mr
560 }
561```
562
5632. Alan's server marks the Friend record as 'accepted' and sends a POST request to Michael's server to notify him that his friend request was accepted
564```
565Michael's Browser Michael's server Alan's server Alan's Browser
566----------------- ---------------- ---------------- ----------------
567
568 <------------------------ POST
569 http://rhodes.com/antisocial/mr/friend-webhook
570 BODY {
571 'action': 'friend-request-accepted'
572 'accessToken': Michael's access token
573 }
574```
575
5763.Michael's server marks the Friend record as 'accepted'
577```
578Michael's Browser Michael's server Alan's server Alan's Browser
579----------------- ---------------- ---------------- ----------------
580
581 RESPONSE ---------------->
582 { 'status':'ok' }
583```
584
5854. Alan's server returns status to the client.
586```
587Michael's Browser Michael's server Alan's server Alan's Browser
588----------------- ---------------- ---------------- ----------------
589
590
591 RESPONSE --------------->
592 { 'status':'ok' }
593```
594
595
596Copyright Michael Rhodes. 2017,2018. All Rights Reserved.
597This file is licensed under the MIT License.
598License text available at https://opensource.org/licenses/MIT