@@ -46,8 +46,8 @@ route AuthApi where
46 = VerifyAuth
46 = VerifyAuth
48route ClientApi where
48route ClientApi where
49− GET /poll headers {authorization: Text}
49+ GET {cursor: Int} /poll headers {authorization: Text}
50− -> {events: [PollEvent], cursor: Text}
50+ -> {events: [PollEvent], cursor: Int}
53 POST {to: Text, encryptedBlob: Text, timestamp: Int} / headers {authorization: Text}
53 POST {to: Text, encryptedBlob: Text, timestamp: Int} / headers {authorization: Text}
@@ -169,7 +169,7 @@ serve = \req -> case req of
170 token <- genToken
170 token <- genToken
171 let expiry = t + 3600000
171 let expiry = t + 3600000
172− sigValid <- atomic do
172+ result <- atomic do
173 pruneExpired t
173 pruneExpired t
174 challenges <- *challenges
174 challenges <- *challenges
175 sessions <- *sessions
175 sessions <- *sessions
@@ -195,16 +195,17 @@ serve = \req -> case req of
195 where s.expiresAt > t
195 where s.expiresAt > t
197 let oldestExpiry = fold (\acc s -> if s.expiresAt < acc then s.expiresAt else acc) expiry existingForKey
197 let oldestExpiry = fold (\acc s -> if s.expiresAt < acc then s.expiresAt else acc) expiry existingForKey
198+ let cappedFold = fold (\acc s ->
199+ if not acc.removed && s.pubkey == pubkey && s.expiresAt == oldestExpiry
200+ then {removed: True {}, sessions: acc.sessions}
201+ else {removed: acc.removed, sessions: union acc.sessions [s]}) {removed: False {}, sessions: []} sessions
198 let sessionsCapped = if count existingForKey >= 10
202 let sessionsCapped = if count existingForKey >= 10
200− s <- sessions
203+ then cappedFold.sessions
201− where not (s.pubkey == pubkey && s.expiresAt == oldestExpiry)
203 else sessions
204 else sessions
204 set *sessions = if sigValid
205 set *sessions = if sigValid
205 then union sessionsCapped [{token: token, pubkey: pubkey, expiresAt: expiry}]
206 then union sessionsCapped [{token: token, pubkey: pubkey, expiresAt: expiry}]
206 else sessions
207 else sessions
207− set *presence = if sigValid
208+ let updatedPresence = if sigValid
208 then union
209 then union
210 p <- presence
211 p <- presence
@@ -212,25 +213,25 @@ serve = \req -> case req of
213 [{pubkey: pubkey, server: serverHost, expiresAt: expiry}]
214 [{pubkey: pubkey, server: serverHost, expiresAt: expiry}]
214 else presence
215 else presence
215− yield sigValid
216+ set *presence = updatedPresence
217+ let peerServers = if sigValid && not (federationSecret == "")
218+ then fold (\acc p ->
219+ if p.server == serverHost then acc
220+ else if count (do a <- acc; where a == p.server; yield a) > 0 then acc
221+ else union acc [p.server]) [] updatedPresence
223+ yield {sigValid: sigValid, peerServers: peerServers}
216− if sigValid
224+ if result.sigValid
217 then respond {token: token, expiresAt: expiry}
225 then respond {token: token, expiresAt: expiry}
218 else respond {token: "", expiresAt: 0}
226 else respond {token: "", expiresAt: 0}
219− if sigValid && not (federationSecret == "")
227+ if result.sigValid && not (federationSecret == "")
221− peerServers <- atomic do
222− presence <- *presence
223− yield (fold (\acc p ->
224− if p.server == serverHost then acc
225− else if count (do a <- acc; where a == p.server; yield a) > 0 then acc
226− else union acc [p.server]) [] presence)
227− sendToEach peerServers (\server ->
228+ then sendToEach result.peerServers (\server ->
228− httpPost (federationScheme ++ server ++ "/federation/gossip")
229+ httpPost (federationScheme ++ server ++ "/federation/gossip")
229− {authorization: "Bearer " ++ federationSecret}
230+ {authorization: "Bearer " ++ federationSecret}
230− {events: [{eventType: "online", pubkey: pubkey, server: serverHost, timestamp: t, ttl: 3600}], hopsRemaining: 2})
231+ {events: [{eventType: "online", pubkey: pubkey, server: serverHost, timestamp: t, ttl: 3600}], hopsRemaining: 2})
231 else yield {}
232 else yield {}
233− Poll {authorization, respond} -> do
234+ Poll {cursor, authorization, respond} -> do
235 let token = extractToken authorization
236 let token = extractToken authorization
236 result <- atomic do
237 result <- atomic do
@@ -244,20 +245,22 @@ serve = \req -> case req of
244 m <- messages
245 m <- messages
245 where m.toKey == s.pubkey
246 where m.toKey == s.pubkey
246 where m.timestamp >= t - messageMaxAge
247 where m.timestamp >= t - messageMaxAge
248+ where m.timestamp >= cursor
247 yield {id: m.messageId, eventType: "message",
249 yield {id: m.messageId, eventType: "message",
248 payload: toJson {toKey: m.toKey, fromKey: m.fromKey,
250 payload: toJson {toKey: m.toKey, fromKey: m.fromKey,
249 encryptedBlob: m.encryptedBlob,
251 encryptedBlob: m.encryptedBlob,
250 timestamp: m.timestamp}}
252 timestamp: m.timestamp}}
251− let cursor = case session of
253+ let newCursor = case session of
252− Nothing {} -> "unauthorized"
254+ Nothing {} -> 0
253− _ -> show t
255+ _ -> t
254− yield {events: evts, cursor: cursor}
256+ yield {events: evts, cursor: newCursor}
255 respond result
257 respond result
257 SendMessage {to, encryptedBlob, timestamp, authorization, respond} -> do
259 SendMessage {to, encryptedBlob, timestamp, authorization, respond} -> do
259 msgId <- genToken
261 msgId <- genToken
260 let clampedTs = if timestamp > t then t else if timestamp < t - messageMaxAge then t else timestamp
262 let clampedTs = if timestamp > t then t else if timestamp < t - messageMaxAge then t else timestamp
263+ let validInput = not (to == "") && not (encryptedBlob == "")
261 result <- atomic do
264 result <- atomic do
262 pruneExpired t
265 pruneExpired t
263 sessions <- *sessions
266 sessions <- *sessions
@@ -266,9 +269,11 @@ serve = \req -> case req of
266 let session = lookupSession sessions (extractToken authorization) t
269 let session = lookupSession sessions (extractToken authorization) t
267 set *messages = case session of
270 set *messages = case session of
268 Nothing {} -> messages
271 Nothing {} -> messages
272+ Just {value: s} -> if validInput
269− Just {value: s} -> union messages
273+ then union messages
270− [{messageId: msgId, toKey: to, fromKey: s.pubkey,
274+ [{messageId: msgId, toKey: to, fromKey: s.pubkey,
271− encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: ""}]
275+ encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: ""}]
272 let localOnline = case session of
277 let localOnline = case session of
273 Nothing {} -> False {}
278 Nothing {} -> False {}
274 Just {value: _} -> count (do
279 Just {value: _} -> count (do
@@ -290,7 +295,8 @@ serve = \req -> case req of
290 Just {value: s} -> s.pubkey
295 Just {value: s} -> s.pubkey
291 let status = case session of
296 let status = case session of
292 Nothing {} -> "unauthorized"
297 Nothing {} -> "unauthorized"
298+ _ -> if not validInput then "invalid"
293− _ -> if localOnline then "delivered"
299+ else if localOnline then "delivered"
294 else if count remoteServers > 0 then "federated"
300 else if count remoteServers > 0 then "federated"
295 else "queued"
301 else "queued"
296 yield {status: status, remoteServers: remoteServers, fromKey: fromKey}
302 yield {status: status, remoteServers: remoteServers, fromKey: fromKey}
@@ -302,7 +308,7 @@ serve = \req -> case req of
302 timestamp: clampedTs, originServer: serverHost, messageId: msgId}
308 timestamp: clampedTs, originServer: serverHost, messageId: msgId}
303 yield (if anyOk then "federated" else "queued")
309 yield (if anyOk then "federated" else "queued")
304 else yield result.status
310 else yield result.status
305− respond {status: finalStatus, messageId: (if result.status == "unauthorized" then "" else msgId)}
311+ respond {status: finalStatus, messageId: (if result.status == "unauthorized" then "" else if result.status == "invalid" then "" else msgId)}
307 AckMessages {messageIds, authorization, respond} -> do
313 AckMessages {messageIds, authorization, respond} -> do
@@ -477,6 +483,7 @@ serve = \req -> case req of
478 let authorized = verifyFederationAuth authorization
484 let authorized = verifyFederationAuth authorization
479 let validOrigin = not (originServer == "") && not (originServer == serverHost)
485 let validOrigin = not (originServer == "") && not (originServer == serverHost)
486+ let validFields = not (toKey == "") && not (fromKey == "") && not (encryptedBlob == "") && not (messageId == "")
480 let clampedTs = if timestamp > t then t else if timestamp < t - messageMaxAge then t else timestamp
487 let clampedTs = if timestamp > t then t else if timestamp < t - messageMaxAge then t else timestamp
481 atomic do
488 atomic do
482 pruneExpired t
489 pruneExpired t
@@ -486,13 +493,13 @@ serve = \req -> case req of
486 where m.messageId == messageId
493 where m.messageId == messageId
487 where m.timestamp >= t - messageMaxAge
494 where m.timestamp >= t - messageMaxAge
488 yield m) > 0
495 yield m) > 0
489− let shouldInsert = authorized && validOrigin && not exists
496+ let shouldInsert = authorized && validOrigin && validFields && not exists
490 set *messages = if shouldInsert
497 set *messages = if shouldInsert
491 then union messages
498 then union messages
492 [{messageId: messageId, toKey: toKey, fromKey: fromKey,
499 [{messageId: messageId, toKey: toKey, fromKey: fromKey,
493 encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: originServer}]
500 encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: originServer}]
494 else messages
501 else messages
495− respond {ok: authorized && validOrigin}
502+ respond {ok: authorized && validOrigin && validFields}
497 FederationAck {messageIds, originServer, fromServer, authorization, respond} -> do
504 FederationAck {messageIds, originServer, fromServer, authorization, respond} -> do
@@ -513,12 +520,6 @@ serve = \req -> case req of
513 where p.server == serverHost
520 where p.server == serverHost
514 where p.expiresAt > t
521 where p.expiresAt > t
515 yield p) == 0
522 yield p) == 0
518− where p.pubkey == m.toKey
519− where p.server == fromServer
520− where p.expiresAt > t
523 else messages
524 else messages
524 respond {ok: validAck}
525 respond {ok: validAck}