|
1 | 1 | # mod\_push\_appserver
|
2 | 2 |
|
3 |
| -Simple and extendable app server for XMPP push notifications as defined in |
4 |
| -[XEP-0357][1]. |
5 |
| - |
6 |
| -The app server is implemented as a module for the [Prosody][2] XMPP server. |
7 |
| - |
8 |
| -Currently, only push notifications to Apple's [APNS][3] and Google's [FCM][4] |
9 |
| -are implemented, but other push services can easily be added in a separate |
10 |
| -module. |
11 |
| - |
12 |
| -## Requirements |
13 |
| - |
14 |
| -- Prosody trunk/0.12 or later. |
15 |
| -- Lua 5.1, 5.2 or 5.3 |
16 |
| -- Installed luasec Lua library version 0.5 (Debian package: `lua-sec`) or higher. |
17 |
| -- Installed cqueues Lua library (Debian package: `lua-cqueues`). |
18 |
| -- Installed lua-http Lua library (Debian package: `lua-http`). |
19 |
| - |
20 |
| -## Installation |
21 |
| - |
22 |
| -Just check out the repository somewhere and point prosody at this directory |
23 |
| -using `plugin_paths` in its main config file. |
24 |
| -For example: `plugin_paths = { "/usr/local/lib/mod_push_appserver" }`. |
25 |
| - |
26 |
| -Then add the submodule you need (for example `mod_push_appserver_apns` |
27 |
| -or `mod_push_appserver_fcm`) to the enabled modules of a specific virtual host or component. |
28 |
| - |
29 |
| -See this configuration example for [APNS][3] if you want to load the needed submodule as component: |
30 |
| -``` |
31 |
| -Component "push.example.org" "push_appserver_apns" |
32 |
| - push_appserver_debugging = false |
33 |
| - push_appserver_apns_sandbox = false |
34 |
| - push_appserver_apns_collapse_pushes = false |
35 |
| - push_appserver_apns_push_priority = "auto" |
36 |
| - push_appserver_apns_mutable_content = true |
37 |
| - push_appserver_apns_cert = "/etc/prosody/apns_normal.crt" |
38 |
| - push_appserver_apns_key = "/etc/prosody/apns_normal.key" |
39 |
| - push_appserver_apns_topic = "my.app.bundle.id" |
40 |
| -
|
41 |
| -Component "voip-push.example.org" "push_appserver_apns" |
42 |
| - push_appserver_debugging = false |
43 |
| - push_appserver_apns_sandbox = false |
44 |
| - push_appserver_apns_collapse_pushes = false |
45 |
| - push_appserver_apns_push_priority = "voip" |
46 |
| - push_appserver_apns_cert = "/etc/prosody/apns_voip.crt" |
47 |
| - push_appserver_apns_key = "/etc/prosody/apns_voip.key" |
48 |
| - push_appserver_apns_topic = "my.app.bundle.id" |
49 |
| -``` |
50 |
| - |
51 |
| -If you want to have only one component host supporting APNS *and* FCM you can use the following: |
52 |
| -``` |
53 |
| -Component "push.example.org" "push_appserver" |
54 |
| - modules_enabled = { |
55 |
| - "push_appserver_fcm"; |
56 |
| - "push_appserver_apns"; |
57 |
| - } |
58 |
| - push_appserver_debugging = false |
59 |
| - push_appserver_fcm_key = "someFCMkey" |
60 |
| - push_appserver_apns_sandbox = false |
61 |
| - push_appserver_apns_collapse_pushes = false |
62 |
| - push_appserver_apns_push_priority = "voip" |
63 |
| - push_appserver_apns_cert = "/etc/prosody/apns_voip1.crt" |
64 |
| - push_appserver_apns_key = "/etc/prosody/apns_voip1.key" |
65 |
| - push_appserver_apns_topic = "my.app.bundle.id" |
66 |
| -``` |
67 |
| - |
68 |
| -Or only use FCM: |
69 |
| -``` |
70 |
| -Component "fcm-push.example.org" "push_appserver_fcm" |
71 |
| - push_appserver_debugging = false |
72 |
| - push_appserver_fcm_key = "myFCMkey" |
73 |
| -``` |
74 |
| - |
75 |
| -## Usage notes (configuration) |
76 |
| - |
77 |
| -For chat apps using VoIP pushes to APNS, the priority should be set to `high`. |
78 |
| -The alert text can be ignored in this case (if you only want to wakeup your |
79 |
| -device). For normal push notifications, the priorities `high` and `silent` are |
80 |
| -supported. The configured alert text (`push_appserver_apns_push_alert`) is |
81 |
| -ignored for `silent` pushes. |
82 |
| - |
83 |
| -For pushes to FCM the priorities `high` and `normal` are supported with `normal` |
84 |
| -priorities being delayed while the device is in doze mode. |
85 |
| -Pushes having priority `high` are always delivered, even in doze mode, thus |
86 |
| -should be used for chat apps. |
87 |
| - |
88 |
| -### Configuration options (mod\_push\_appserver) |
89 |
| - |
90 |
| -- **push\_appserver\_debugging** *(boolean)* |
91 |
| - Make `/push_appserver/v1/settings` HTTP endpoint available. Default: `false`. |
92 |
| - This setting will also make http forms available at all `POST` HTTP endpoints |
93 |
| - for easier manual testing of your setup by simply using your browser of choice. |
94 |
| -- **push\_appserver\_rate\_limit** *(number)* |
95 |
| - Allow only one request everey N seconds. Default: `5`. |
96 |
| - The throttle will always space out incoming requests by this timeframe and make sure |
97 |
| - that the last request of a burst will always be handled at the beginning of the next |
98 |
| - timeframe (the first request of a burst will be handled immediately, all other requests |
99 |
| - in between the first and the last one will be ignored completely). |
100 |
| - This should mitigate some DOS attacks. |
101 |
| -- **push\_appserver\_store\_plugin** *(string)* |
102 |
| - Set this to the store plugin to use, currently available: `cached`, `uncached`. |
103 |
| - Use the `uncached` store to deactivate local caching of device tokens and node settings. |
104 |
| - This is useful if you are using an SQL storage backend and this backend is |
105 |
| - replicated across several servers in an HA- or loadbalancing-setup. |
106 |
| - Default: `cached`. |
107 |
| -- **push\_appserver\_store\_params** *(table)* |
108 |
| - Set this to anything you want to pass to the storage plugin. |
109 |
| - The two default implementations `cached`, `uncached` don't take any params. |
110 |
| - Default: `nil`. |
111 |
| - |
112 |
| -### Configuration options (mod\_push\_appserver\_apns) |
113 |
| - |
114 |
| -- **push\_appserver\_apns\_cert** *(string)* |
115 |
| - Path to your APNS push certificate in PEM format. |
116 |
| -- **push\_appserver\_apns\_key** *(string)* |
117 |
| - Path to your APNS push certificate key in PEM format. |
118 |
| -- **push\_appserver\_apns\_topic** *(string)* |
119 |
| - The APNS push topic to use (should be your app's bundle id). |
120 |
| -- **push\_appserver\_apns\_capath** *(string)* |
121 |
| - Path to CA certificates directory. Default: `"/etc/ssl/certs"` (Debian and |
122 |
| - Ubuntu use this path for the system CA store). |
123 |
| -- **push\_appserver\_apns\_ciphers** *(string)* |
124 |
| - Ciphers to use when establishing a tls connection. Default: |
125 |
| - `ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256` |
126 |
| -- **push\_appserver\_apns\_sandbox** *(boolean)* |
127 |
| - Use apns sandbox api endpoint if `true`, production endpoint otherwise. |
128 |
| - Default: `true`. |
129 |
| -- **push\_appserver\_apns\_collapse\_pushes** *(boolean)* |
130 |
| - Instruct Apple to collapse queued pushes into one single push (`true`), or keep them separated (`false`). |
131 |
| - Setting this to false allows to get more background time for your app when sending multiple pushes in a row |
132 |
| - (this can be subject to implementation changes by Apple, though). |
133 |
| - Default: `false`. |
134 |
| -- **push\_appserver\_apns\_mutable\_content** *(boolean)* |
135 |
| - Mark high prio pushes as mutable content (only has a meaningful effect if `push_appserver_apns_push_priority` is set to |
136 |
| - `"high"` or `"auto"`). Default: `true`. |
137 |
| -- **push\_appserver\_apns\_push\_ttl** *(number)* |
138 |
| - TTL for push notification in seconds. Default: `4*7*24*3600` (that means 4 weeks from now). |
139 |
| -- **push\_appserver\_apns\_push\_priority** *(string)* |
140 |
| - Value `"high"` for high priority pushes always triggering a visual indication on the user's phone, |
141 |
| - `"silent"` for silent pushes that can be delayed or not delivered at all but don't trigger |
142 |
| - a visual indication, `voip` for voip pushes and `"auto"` to let the appserver automatically decide between `"high"` and `"silent"` |
143 |
| - based on the presence of `"last-message-body"` in the push summary received from the XMPP server. Default: `"auto"`. |
144 |
| - **NOTE 1 (iOS >= 13):** Apple decided for iOS >= 13 to not allow silent voip pushes anymore. Use `"high"` or `"auto"` on |
145 |
| - this systems and set `push_appserver_apns_mutable_content` to `true`. Then use a `Notification Service Extension` in your app |
146 |
| - to log in into your XMPP account in the background, retrieve the acutal stanzas and replace the notification with a useful |
147 |
| - one before the dummy notification sent by this appserver hits the screen. |
148 |
| - **NOTE 2 (iOS >= 10 and < 13):** if you have VoIP capabilities in your app `"silent"` pushes will become reliable and always |
149 |
| - wake up your app without triggering any visual indications on the user's phone. |
150 |
| - In VoIP mode your app can decide all by itself if it wants to show a notification to the user or not |
151 |
| - by simply logging into the XMPP account in the backround and retrieving the stanzas that triggered the push. |
152 |
| - *You have to use `voip` if you want to send voip pushes (all iOS versions).* |
153 |
| - |
154 |
| -### Configuration options (mod\_push\_appserver\_fcm) |
155 |
| - |
156 |
| -- **push\_appserver\_fcm\_key** *(string)* |
157 |
| - Your FCM push credentials (can be found in FCM dashboard under Settings --> Cloud Messaging --> Server key). |
158 |
| -- **push\_appserver\_fcm\_capath** *(string)* |
159 |
| - Path to CA certificates directory. Default: `"/etc/ssl/certs"` (Debian and |
160 |
| - Ubuntu use this path for the system CA store). |
161 |
| -- **push\_appserver\_fcm\_ciphers** *(string)* |
162 |
| - Ciphers to use when establishing a tls connection. Default: |
163 |
| - `ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256` |
164 |
| -- **push\_appserver\_fcm\_push\_ttl** *(number)* |
165 |
| - TTL for push notification in seconds, can be 4 weeks at max. |
166 |
| - Default: `nil` (that means 4 weeks). |
167 |
| -- **push\_appserver\_fcm\_push\_priority** *(string)* |
168 |
| - Value `"high"` for high priority pushes that wake up the device even when |
169 |
| - in doze mode or `"normal"` for normal pushes that can be delayed. |
170 |
| - Default: `"high"`. |
171 |
| - |
172 |
| -## API (register device etc.) |
173 |
| - |
174 |
| -This appserver implements [XEP-0357][1] commands for sending actual push notifications. |
175 |
| -Additionally [XEP-0357][1] requires a device to be registered on the appserver but does |
176 |
| -not dictate how this should be done. |
177 |
| - |
178 |
| -**Therefore this appserver provides two different APIs to register a (new) device on the appserver.** |
179 |
| -**Just use the one more convenient to you.** |
180 |
| - |
181 |
| -### [XEP-0050][6] Ad-Hoc Command API |
182 |
| - |
183 |
| -This API resembles more or less what Conversations is doing, |
184 |
| -but with some small differences: |
185 |
| -- the command is named "v1-register-push" instead of "register-push-gcm" |
186 |
| -- the device id field is called "node" instead of "device-id" |
187 |
| -- the new field "type" was added. This can be used to specify the push |
188 |
| - type just as it is done using the http based API. |
189 |
| -- unregistering a device is not done at all in Conversations, but this appserver supports it via the v1-unregister-push command |
190 |
| - |
191 |
| -[This Gist][5] demonstrates the changes needed to Conversations to use this appserver |
192 |
| -instead of inputmice's p2. |
193 |
| -See [XEP-0050][6] for more info regarding Ad-Hoc Commands in general. |
194 |
| - |
195 |
| -**Keep in mind that the registration/unregistration commands sent to this appserver are routed through the user's xmpp server.** |
196 |
| -**This exposes the raw APNS/FCM push token and device id to the user's xmpp server.** |
197 |
| -**Use the HTTP API if you don't like this (HTTP will expose the user's IP, though).** |
198 |
| - |
199 |
| -#### Example XMPP flow for registering a device: |
200 |
| -``` |
201 |
| -<iq to="push.example.org" id="MyID-6465" type="set"> |
202 |
| - <command xmlns="http://jabber.org/protocol/commands" node="v1-register-push" action="execute"> |
203 |
| - <x xmlns="jabber:x:data" type="submit"> |
204 |
| - <field type='hidden' var='FORM_TYPE'> |
205 |
| - <value>https://github.com/tmolitor-stud-tu/mod_push_appserver/#v1-register-push</value> |
206 |
| - </field> |
207 |
| - <field var="type"> |
208 |
| - <value>fcm</value> |
209 |
| - </field> |
210 |
| - <field var="node"> |
211 |
| - <value>static device id like ANDROID_ID OR some stable iOS id</value> |
212 |
| - </field> |
213 |
| - <field var="token"> |
214 |
| - <value>dynamic token obtained from FirebaseInstanceId_InstanceId OR apns token</value> |
215 |
| - </field> |
216 |
| - </x> |
217 |
| - </command> |
218 |
| -</iq> |
219 |
| -
|
220 |
| -<iq to="[email protected]/res1" from="push.example.org" type="result" id="MyID-6465"> |
221 |
| - <command xmlns="http://jabber.org/protocol/commands" status="complete" node="v1-register-push" sessionid="1559985918910"> |
222 |
| - <x xmlns="jabber:x:data" type="form"> |
223 |
| - <field type="jid-single" var="jid"> |
224 |
| - <value>push.example.org</value> |
225 |
| - </field> |
226 |
| - <field type="text-single" var="node"> |
227 |
| - <value>echoed back static device id</value> |
228 |
| - </field> |
229 |
| - <field type="text-single" var="secret"> |
230 |
| - <value>some arbitrary hash-like value</value> |
231 |
| - </field> |
232 |
| - </x> |
233 |
| - </command> |
234 |
| -</iq> |
235 |
| -``` |
236 |
| - |
237 |
| -The two values `node` and `secret` are needed for registering push on the XMPP server afterwards, |
238 |
| -see [example 9 in XEP-0357, section 5][7]. |
239 |
| - |
240 |
| -#### Example XMPP flow for UNregistering a device: |
241 |
| -``` |
242 |
| -<iq to="push.example.org" id="MyID-6446" type="set"> |
243 |
| - <command xmlns="http://jabber.org/protocol/commands" node="v1-unregister-push" action="execute"> |
244 |
| - <x xmlns="jabber:x:data" type="submit"> |
245 |
| - <field type='hidden' var='FORM_TYPE'> |
246 |
| - <value>https://github.com/tmolitor-stud-tu/mod_push_appserver/#v1-unregister-push</value> |
247 |
| - </field> |
248 |
| - <field var="type"> |
249 |
| - <value>fcm</value> |
250 |
| - </field> |
251 |
| - <field var="node"> |
252 |
| - <value>static device id like ANDROID_ID OR some stable iOS id</value> |
253 |
| - </field> |
254 |
| - </x> |
255 |
| - </command> |
256 |
| -</iq> |
257 |
| -
|
258 |
| -<iq to="[email protected]/res1" from="push.example.org" type="result" id="MyID-6446"> |
259 |
| - <command xmlns="http://jabber.org/protocol/commands" status="complete" node="v1-unregister-push" sessionid="1559985918910"> |
260 |
| - <x xmlns="jabber:x:data" type="form"> |
261 |
| - <field type="jid-single" var="jid"> |
262 |
| - <value>push.example.org</value> |
263 |
| - </field> |
264 |
| - <field type="text-single" var="node"> |
265 |
| - <value>echoed back static device id</value> |
266 |
| - </field> |
267 |
| - </x> |
268 |
| - </command> |
269 |
| -</iq> |
270 |
| -``` |
271 |
| - |
272 |
| -### HTTP API |
273 |
| - |
274 |
| -All `POST` endpoints can be used via `GET` to get back a simple html page which |
275 |
| -allows you to manually test the endpoint behaviour in your browser, if the config |
276 |
| -option `push_appserver_debugging` is set to true (an error is returned otherwise). |
277 |
| -*This config option should be false in production environments!* |
278 |
| - |
279 |
| -- POST to `http://<host>:5280/push_appserver/v1/register` or |
280 |
| - `https://<host>:5281/push_appserver/v1/register` |
281 |
| - POST data: `type=<push type>&node=<device uuid>&token=<apns/fcm/etc. push token>` |
282 |
| - function: register device for push |
283 |
| - result: text document separated by `\n` |
284 |
| - - first line: `OK`, everything else (including `ERROR`) is specified as error |
285 |
| - condition |
286 |
| - if ok: 2nd line: XEP-0357 push `node`, 3rd line: XEP-0357 push `secret` |
287 |
| - if error: 2nd and subsequent lines: error description |
288 |
| - |
289 |
| -- POST to `http://<host>:5280/push_appserver/v1/unregister` or |
290 |
| - `https://<host>:5281/push_appserver/v1/unregister` |
291 |
| - POST data: `type=<push type>&node=<device uuid>` |
292 |
| - function: unregister device |
293 |
| - result: text document separated by `\n` |
294 |
| - - first line: `OK`, everything else (including `ERROR`) is specified as error |
295 |
| - condition |
296 |
| - if ok: 2nd line: XEP-0357 push `node`, 3rd line: XEP-0357 push `secret` |
297 |
| - if error: 2nd and subsequent lines: error description |
298 |
| - |
299 |
| -- POST to `http://<host>:5280/push_appserver/v1/push` or |
300 |
| - `https://<host>:5281/push_appserver/v1/push` |
301 |
| - POST data: `node=<device uuid>&secret=<secret obtained on register>` |
302 |
| - function: send push notification to device |
303 |
| - result: text document separated by `\n` |
304 |
| - - first line: `OK`, everything else (including `ERROR`) is specified as error |
305 |
| - condition |
306 |
| - if ok: 2nd line: XEP-0357 push `node` |
307 |
| - if error: 2nd and subsequent lines: error description |
308 |
| - |
309 |
| -- GET to `http://<host>:5280/push_appserver/v1/settings` or |
310 |
| - `https://<host>:5281/push_appserver/v1/settings` |
311 |
| - function: get list of registered device UUIDs |
312 |
| - result: html site listing all registered device UUIDS as links |
313 |
| - |
314 |
| -- GET to `http://<host>:5280/push_appserver/v1/settings/<device uuid>` or |
315 |
| - `https://<host>:5281/push_appserver/v1/settings/<device uuid>` |
316 |
| - function: get internal data saved for this device UUID |
317 |
| - result: HTML site listing all data (serialized Lua table using penlight's |
318 |
| - `pl.pretty`) |
319 |
| - |
320 |
| -- GET to `http://<host>:5280/push_appserver/v1/health` or |
321 |
| - `https://<host>:5281/push_appserver/v1/health` |
322 |
| - function: get health status of module |
323 |
| - result: html site containing the word `RUNNING` if the module is loaded properly |
324 |
| - (this GET-node is accessible even when `push_appserver_debugging` is set to `false`) |
325 |
| - |
326 |
| -## Implementation notes |
327 |
| - |
328 |
| -mod\_push\_appserver and its submodules use events to communicate with each |
329 |
| -other. These events are documented here. |
330 |
| - |
331 |
| -### Interaction between mod\_push\_appserver and its submodules |
332 |
| - |
333 |
| -mod\_push\_appserver triggers the event `incoming-push-to-<push type>` |
334 |
| -(currently only the types `apns` and `fcm` are supported). |
335 |
| -The event handler has to return `true` or an error description string |
336 |
| -for failed push attempts and `false` for successfull ones. |
337 |
| -Returning `nil` will be handled as error! |
338 |
| -The event data always includes the following keys: |
339 |
| - |
340 |
| -- **origin** |
341 |
| - Prosody session the stanza came from (typically an s2s session). |
342 |
| -- **settings** |
343 |
| - The registered push settings which are also available at the |
344 |
| - `/push_appserver/v1/settings/<device uuid>` HTTP endpoint in debug mode. |
345 |
| -- **summary** |
346 |
| - The push summary (see [XEP-0357][1] for more information) |
347 |
| -- **stanza** |
348 |
| - The incoming push stanza (see [XEP-0357][1] for more information). |
349 |
| - |
350 |
| -Submodules (like mod\_push\_appserver\_apns) can trigger the event |
351 |
| -`unregister-push-token`. The event data has to include the following keys: |
352 |
| - |
353 |
| -- **token** |
354 |
| - The push token to invalidate (note: this is not the secret obtained by |
355 |
| - registering the device, but the raw token obtained from APNS, FCM etc.). |
356 |
| -- **type** |
357 |
| - `apns`, `fcm` etc. |
358 |
| -- **timestamp** |
359 |
| - The timestamp of the delete request. mod\_push\_appserver won't unregister the |
360 |
| - token if it was re-registered after this timestamp. |
361 |
| - |
362 |
| -### Example of internal data |
363 |
| - |
364 |
| -```lua |
365 |
| -{ |
366 |
| - type = "apns", |
367 |
| - token = "DEADBEEFABCDEF0123456DEADBEEF112DEADBEEFABCDEF0123456DEADBEEF112", |
368 |
| - last_push_error = "2017-03-18T04:07:44Z", |
369 |
| - last_successful_push = "2017-03-18T03:54:24Z", |
370 |
| - registered = "2017-03-17T02:10:21Z", |
371 |
| - renewed = "2017-03-18T02:54:51Z", |
372 |
| - node = "E0FF1D8C-EB96-4E10-A912-F68B03FD8D3E", |
373 |
| - secret = "384e51b4b2d5e4758e5dc342b22dea9217212f2c4886e2a3dcf16f3eb0eb3807" |
374 |
| -} |
375 |
| -``` |
376 |
| - |
377 |
| -## Used By (incomplete list) |
378 |
| -- [Monal (iOS)][8] |
379 |
| -- [yaxim (Android)][9] |
380 |
| - |
381 |
| -[1]: https://xmpp.org/extensions/xep-0357.html |
382 |
| -[2]: https://prosody.im/ |
383 |
| -[3]: https://developer.apple.com/go/?id=push-notifications |
384 |
| -[4]: https://firebase.google.com/docs/cloud-messaging/ |
385 |
| -[5]: https://gist.github.com/tmolitor-stud-tu/a1e877a7d75c07c2163c3ce1e0347881 |
386 |
| -[6]: https://xmpp.org/extensions/xep-0050.html |
387 |
| -[7]: https://xmpp.org/extensions/xep-0357.html#example-9 |
388 |
| -[8]: https://monal.im/ |
389 |
| -[9]: https://yaxim.org/ |
| 3 | +This project is not maintained anymore, use the follow-up project over here: https://github.com/monal-im/fpush |
0 commit comments