UNPKG

28.7 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 that needs to support building and maintaining antisocial 'friend' relationships 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
45The URL form of the endpoints confirm to the collowing conventions:
46
47`api-prefix` is the location where antisocial-friends is mounted defined in config
48
49`local-username` is a unique username for the local user
50
51Endpoints are URLS that are the base address of a user on an antisocial aware server.
52
53'https://some.antisocial.server/api-prefix/local-username'
54
55### On the requestor's server
56
57#### make a friend request
58```
59GET /api-prefix/local-username/request-friend
60 Query params:
61 endpoint: endpoint url of friend to connect with
62 invite: an invite token if this is in response to an invitation from the user
63```
64
65#### cancel a pending friend request
66```
67POST /api-prefix/local-username/request-friend-cancel
68 Request Body: (application/x-www-form-urlencoded)
69 endpoint: endpoint of the pending request
70```
71
72### On the requestee's server
73
74#### accept a pending friend request
75```
76/api-prefix/local-username/friend-request-accept
77 Request Body: (application/x-www-form-urlencoded)
78 endpoint: endpoint of the pending request
79```
80
81#### decline a pending friend request
82```
83/api-prefix/local-username/friend-request-decline
84 Request Body: (application/x-www-form-urlencoded)
85 endpoint: endpoint of the pending request
86```
87
88### either side can update or delete the relationship once accepted
89```
90POST /api-prefix/local-username/friend-update
91 Request Body: (application/x-www-form-urlencoded)
92 endpoint: endpoint of the friend
93 status: 'delete'|'block'
94 audiences: array of audiences for the friend eg. ["public","friends","some custom audience"]
95```
96
97## socket.io feeds
98
99Accepted friends establish socket.io connections to update eachother 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 events received by the application.
100
101## Events
102You can handle the following events as needed. For example, to notify user about a friend request, start watching feeds etc.
103
104### new-friend-request event: a new friend request received
105Relevant details are in e.user (a user instance) and e.friend (a friend instance). Typically would be used to notify the user of the pending friend request.
106```
107antisocialApp.on('new-friend-request', function (e) {
108 console.log('antisocial new-friend-request %s %s', e.user.username, e.friend.remoteEndPoint);
109});
110```
111
112### new-friend event: a new friend
113Relevant details are in e.user (a user instance) and e.friend (a friend instance). Typically would be used to notify the user's friends that they have a new friend.
114```
115antisocialApp.on('new-friend', function (e) {
116 console.log('antisocial new-friend %j', e.friend.remoteEndPoint);
117});
118```
119
120### friend-updated event: the relationship has changed
121Relevant details are in e.user (a user instance) and e.friend (a friend instance). This might be a change of server address or the friend might have changed the audiences for the user. Typically user would remove cache of notifications and re-load.
122```
123antisocialApp.on('friend-updated', function (e) {
124 console.log('antisocial friend-updated %j', e.friend.remoteEndPoint);
125});
126```
127
128### friend-deleted event: either user or the friend has deleted the other
129Typically user would clean up the database and remove anything about or by the friend.
130```
131antisocialApp.on('friend-deleted', function (e) {
132 console.log('antisocial friend-deleted %j', e.friend.remoteEndPoint);
133});
134```
135
136### open-activity-connection event: a friend activity feed has been connected.
137Hook to allow app to set up a data handler for messages received on this socket. Typically would hook up any process that would create activity entries to be transmitted to friends and to hook up functions to receive and process activity from friends.
138```
139antisocialApp.on('open-activity-connection', function (e) {
140 console.log('antisocial new-activity-connection for %s %s', e.info.user.username, e.info.friend.remoteEndPoint);
141
142 var friend = e.info.friend;
143 var user = e.info.user;
144 var socket = e.socket;
145
146 // set up data handler. will be called whenever data is received on socket
147 socket.antisocial.setDataHandler(function (data) {
148 console.log('antisocial activity-data from %s to %s %j', friend.remoteName, user.name, data);
149 });
150});
151```
152
153### activity-backfill event. a friend is requesting updates that have occurred since they last connected
154Would typically used to emit 'data' events on the socket or each applicable activity that happened after the date specified in highwater.
155```
156antisocialApp.on('activity-backfill', function (e) {
157 var user = e.info.user;
158 var friend = e.info.friend;
159 var highwater = e.highwater;
160 var socket = e.socket;
161 console.log('antisocial activity-backfill user %s of friend %s requesting backfill since %s', user.username, friend.remoteEndPoint, highwater);
162});
163```
164
165### close-activity-connection: activity feed closed.
166Typically used to clean up any event handlers set up in open-activity-connection.
167```
168antisocialApp.on('close-activity-connection', function (e) {
169 console.log('antisocial new-activity-connection %j', e.info.key);
170});
171```
172
173### open-notification-connection event: The user has opened the notification feed.
174Hook to allow app to set up a data handler for messages received on this socket. Typically used by the application to notify the user's client app or browser of relevant activity events.
175```
176antisocialApp.on('open-notification-connection', function (e) {
177 console.log('antisocial new-notification-connection %j', e.info.key);
178 socket.antisocial.setDataHandler(function (data) {
179 console.log('antisocial notification-data from %s to %s %j', e.info.user.name, data);
180 });
181});
182```
183
184### notification-backfill event. A user is requesting updates that have occurred since they last connected
185Would typically used to emit 'data' events on the socket or each applicable notifications that happened after the date specified in highwater.
186```
187antisocialApp.on('notification-backfill', function (e) {
188 var user = e.info.user;
189 var socket = e.socket;
190 var highwater = e.highwater;
191 console.log('antisocial notification-backfill user %s requesting backfill since %s', user.username, highwater);
192});
193```
194
195
196### close-notification-connection event
197```
198antisocialApp.on('close-notification-connection', function (e) {
199 console.log('antisocial new-notification-connection %j', e.info.key);
200});
201```
202
203
204## The data structures maintained by these protocols
205This app uses the following data collections:
206
207* users: username property is required to build urls
208* friends: several properties maintained by the antisocial protocol
209* invitations: use to simplify "be my friend" invitations
210* blocks: list of blocked friends
211
212friends, invitations and blocks are related to users by a foreign key `userId`
213which is set to the id of the appropriate user when created.
214
215The schema definition is implementation specific and up to the implementor.
216The following is an example db adaptor for a Loopback.io application. dbHandlers
217must support all the methods in this example.
218```
219function dbHandler() {
220 var self = this;
221
222 self.models = {
223 'users': 'MyUser',
224 'friends': 'Friend',
225 'invitations': 'Invite',
226 'blocks': 'Block'
227 };
228
229 // store an item
230 this.newInstance = function (collectionName, data, cb) {
231 server.models[self.models[collectionName]].create(data, function (err, instance) {
232 if (cb) {
233 cb(err, instance);
234 }
235 else {
236 return instance;
237 }
238 });
239 };
240
241 // get an item by matching some properties.
242 // pairs are a list of property/value pairs that are anded
243 // when querying the database example:
244 // [
245 // { 'property':'userId', 'value': 1 },
246 // { 'property':'localAccessToken', 'value': 'jhgasdfjhgsdfjhg' }
247 // ]
248 this.getInstances = function (collectionName, pairs, cb) {
249 var query = {
250 'where': {
251 'and': []
252 }
253 };
254
255 for (var i = 0; i < pairs.length; i++) {
256 var prop = pairs[i].property;
257 var value = pairs[i].value;
258 var pair = {};
259 pair[prop] = value;
260 query.where.and.push(pair);
261 }
262
263 server.models[self.models[collectionName]].find(query, function (err, found) {
264 if (cb) {
265 cb(err, found);
266 }
267 else {
268 return found;
269 }
270 });
271 };
272
273 // update item properties by id
274 this.updateInstance = function (collectionName, id, patch, cb) {
275 server.models[self.models[collectionName]].findById(id, function (err, instance) {
276 if (err) {
277 return cb(new Error('error reading ' + collectionName));
278 }
279 if (!instance) {
280 return cb(new Error('error ' + collectionName + ' id ' + id + ' not found'));
281 }
282
283 instance.updateAttributes(patch, function (err, updated) {
284 if (err) {
285 return cb(new Error('error updating ' + collectionName));
286 }
287 if (cb) {
288 cb(null, updated);
289 }
290 else {
291 return updated;
292 }
293 });
294 });
295 };
296
297 this.deleteInstance = function (collectionName, id, cb) {
298 server.models[self.models[collectionName]].destroyById(id, function (err) {
299 if (cb) {
300 cb(err);
301 }
302 });
303 };
304}
305
306db = new dbHandler();
307```
308
309### User properties
310The user is application specific but we expect the following properties
311```
312{
313 "name": "user one",
314 "username": "user-one",
315 "id": "c5503436-634c-461f-9d90-ba9e438516c1"
316}
317```
318
319### Friend Invitation properties
320```
321{
322 "token": "testinvite",
323 "userId": "53757323-8555-4ef5-b66c-a58351ce6181",
324 "id": "6165e25e-2c31-47d3-a8c2-7c75737d4003"
325}
326```
327### Block list properties
328```
329{
330 "remoteEndPoint": "http://127.0.0.1:3000/antisocial/user-three",
331 "userId": "53757323-8555-4ef5-b66c-a58351ce6181",
332 "id": "7281bcd3-94f8-4906-9714-89d7e5b5c349"
333}
334```
335
336### Friend Properties
337The 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.
338
339```
340[
341 {
342 "originator": true,
343 "status": "accepted",
344 "remoteEndPoint": "http://127.0.0.1:3000/antisocial/user-two",
345 "remoteHost": "http://127.0.0.1:3000",
346 "localRequestToken": "cbf875ad-5eb7-43e4-8028-415ddf6d95a9",
347 "localAccessToken": "fbb8d3be-b199-45a6-b46e-3bcbbfedd0aa",
348 "keys": {
349 "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",
350 "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"
351 },
352 "audiences": ["public", "friends"],
353 "hash": "f08bf78",
354 "userId": "7ca18fd6-5d23-4930-8b64-3812f3a8f012",
355 "inviteToken": "testinvite",
356 "id": "97c46a56-c323-4941-bf61-e2c959748617",
357 "remoteAccessToken": "aabaa39b-cb60-4f29-948f-8483bc29f21c",
358 "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",
359 "remoteName": "user two",
360 "remoteUsername": "user-two",
361 "uniqueRemoteUsername": "user-two"
362 },
363 {
364 "status": "accepted",
365 "remoteRequestToken": "cbf875ad-5eb7-43e4-8028-415ddf6d95a9",
366 "remoteEndPoint": "http://127.0.0.1:3000/antisocial/user-one",
367 "remoteHost": "http://127.0.0.1:3000",
368 "localRequestToken": "97e2a0b7-7a0d-46cb-8be6-4253b842067a",
369 "localAccessToken": "aabaa39b-cb60-4f29-948f-8483bc29f21c",
370 "keys": {
371 "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",
372 "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"
373 },
374 "audiences": ["public", "friends"],
375 "hash": "64aeb3ef",
376 "userId": "700acf91-4ee1-4aba-8e18-136e0cc33560",
377 "inviteToken": "testinvite",
378 "id": "771cec97-d8ab-4ae9-b3d4-7c29521a4732",
379 "remoteAccessToken": "fbb8d3be-b199-45a6-b46e-3bcbbfedd0aa",
380 "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",
381 "remoteName": "user one",
382 "remoteUsername": "user-one",
383 "uniqueRemoteUsername": "user-one"
384 }
385]
386```
387
388## AntiSocial Friend Protocol
389
390protocol for making a friend request
391------------------------------------
392```
393requester sets up pending Friend data on requester's server (/request-friend)
394 requester calls requestee with a requestToken
395 requestee sets up pending Friend data on requestee's server (/friend-request)
396 requestee calls requester to exchange the requestToken for an accessToken and publicKey (/friend-exchange-token)
397 requestee returns requestToken to requester
398 requestee triggers 'new-friend-request' event for requestee application
399 requester calls requestee to exchange requestToken for accessToken and publicKey (/friend-exchange-token)
400```
401
402protocol for accepting a friend request
403---------------------------------------
404```
405requestee marks requester as accepted and grants access to 'public' and 'friends' (/friend-request-accept)
406 requestee calls requester to update status (/friend-webhook action=friend-request-accepted)
407 requester marks requestee as accepted and grants access to 'public' and 'friends'
408 trigger a 'new-friend' event for requestor application
409 trigger a 'new-friend' event for requestee application
410```
411
412At 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.
413
414`accessToken` is a uuid that is used to authenticate connection requests, `requestToken` is a uuid which is used to retrieve an accessToken
415
416
417### Friend Request
418---
419
420Use case: Michael wants to friend Alan and already knows the address of Alan's server antisocial endpoint.
421`http://emtage.com/antisocial/ae`
422
423Michael logs on to his account on his server.
424
425Michael enters Alan's address on the friend request form.
426
427Alan's public profile information is displayed to confirm that the address is correct.
428
429Michael clicks the 'add friend' button which starts the friend request protocol.
430
431### FRIEND REQUEST
432---
433
4341. 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.
435```
436Michael's Browser Michael's server Alan's server Alan's Browser
437----------------- ---------------- ---------------- ----------------
438
439GET --------------------->
440http://rhodes.com/antisocial/mr/request-friend?endpoint=http://emtage.com/antisocial/ae
441```
442
4432. Michael's server sends a POST request to Alan's server to initiate the friend request.
444```
445Michael's Browser Michael's server Alan's server Alan's Browser
446----------------- ---------------- ---------------- ----------------
447
448 POST -------------------->
449 http://emtage.com/antisocial/ae/friend-request
450 BODY {
451 'remoteEndPoint': 'http://rhodes.com/antisocial/mr',
452 'requestToken': Michaels Request Token
453 }
454```
4553. Alans's server connects to Michael's server to validate the origin of the request and to exchange Michael's requestToken for an accessToken.
456```
457Michael's Browser Michael's server Alan's server Alan's Browser
458----------------- ---------------- ---------------- ----------------
459
460 <------------------------ POST
461 http://rhodes.com/antisocial/mr/friend-exchange
462 BODY {
463 'endpoint': 'http://emtage.com/antisocial/ae',
464 'requestToken': Michaels Request Token
465 }
466```
4674. Michael's server looks up the friend record and returns access credentials to Alan's server
468```
469Michael's Browser Michael's server Alan's server Alan's Browser
470----------------- ---------------- ---------------- ----------------
471
472 RESPONSE ---------------->
473 {
474 'status': 'ok',
475 'accessToken': Michael's Access Token,
476 'publicKey': Michael's public key
477 }
478```
4795. 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.
480```
481Michael's Browser Michael's server Alan's server Alan's Browser
482----------------- ---------------- ---------------- ----------------
483
484 <------------------------ RESPONSE
485 {
486 'status': 'ok',
487 'requestToken': Alan's RequestToken
488 }
489```
4906. Michael's server connects to Alan's server to exchange Alan's request token for an accessToken
491```
492Michael's Browser Michael's server Alan's server Alan's Browser
493----------------- ---------------- ---------------- ----------------
494
495 POST ------------------->
496 http://emtage.com/antisocial/ae/friend-exchange
497 BODY {
498 'endpoint': http://rhodes.com/antisocial/mr,
499 'requestToken': Alan's Request Token
500 }
501```
5027. Alan's server looks up the friend record by the requestToken and returns access credentials to Michael's server
503```
504Michael's Browser Michael's server Alan's server Alan's Browser
505----------------- ---------------- ---------------- ----------------
506
507 <------------------------ RESPONSE
508 {
509 'status': 'ok',
510 'accessToken': Alan's AccessToken,
511 'publicKey': Alan's public key
512 }
513```
514
5158. Michael's server saves Alan's accessToken and the publicKey in the pending Friend record and returns status to the client.
516```
517Michael's Browser Michael's server Alan's server Alan's Browser
518----------------- ---------------- ---------------- ----------------
519
520<------------------------ RESPONSE
521 { 'status':'ok' }
522```
523
524### FRIEND ACCEPT
525---
526
5271. Alan accepts friend Michael's request by clicking the button in the UI calling the accept-friend endpoint
528```
529Michael's Browser Michael's server Alan's server Alan's Browser
530----------------- ---------------- ---------------- ----------------
531
532 <----------------------- POST
533 http://emtage.com/antisocial/ae/friend-request-accept
534
535 BODY { 'endpoint': http://rhodes.com/antisocial/mr
536 }
537```
538
5392. 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
540```
541Michael's Browser Michael's server Alan's server Alan's Browser
542----------------- ---------------- ---------------- ----------------
543
544 <------------------------ POST
545 http://rhodes.com/antisocial/mr/friend-webhook
546 BODY {
547 'action': 'friend-request-accepted'
548 'accessToken': Michael's access token
549 }
550```
551
5523.Michael's server marks the Friend record as 'accepted'
553```
554Michael's Browser Michael's server Alan's server Alan's Browser
555----------------- ---------------- ---------------- ----------------
556
557 RESPONSE ---------------->
558 { 'status':'ok' }
559```
560
5614. Alan's server returns status to the client.
562```
563Michael's Browser Michael's server Alan's server Alan's Browser
564----------------- ---------------- ---------------- ----------------
565
566
567 RESPONSE --------------->
568 { 'status':'ok' }
569```
570
571
572Copyright Michael Rhodes. 2017,2018. All Rights Reserved.
573This file is licensed under the MIT License.
574License text available at https://opensource.org/licenses/MIT