<!--
  - SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  - SPDX-License-Identifier: AGPL-3.0-or-later
-->
# Push notifications as a Nextcloud client device

## Introduction

> Why is push-notifications.nextcloud.com necessary?

The Nextcloud mobile apps from the Google Play and Apple App Store are signed with Nextcloud developer keys or certificates.
Push notifications sent to those devices need to be signed with a generated push key or certificate from the same developer account.
The keys and certificates can not be shipped with the Nextcloud server as otherwise everyone would have our developer key and could manipulate releases or push to any random Nextcloud device.
The Firebase Cloud Messaging (Google) and Apple Push Notification Service are not made for something like a federated project like Nextcloud and still assume there is a single entity behind them like with all the other services.

So we created the push proxy push-notifications.nextcloud.com to protect our users and their data.
We took some extra efforts and reduced the available information to a bare minimum for each of the sections.

* Nextcloud server
  - Knowledge:
    + user public and private key (generated by Nextcloud server)
    + device identifier (generated by Nextcloud server)
    + device public key (generated by mobile device)
    + push-token-hash (generated by mobile device)
  - Actions
    + Encrypts the content of the push notifications with `device public key`.
    + Signs it with the `user private key`.
    + Sends the notifications with `push-token-hash` to the proxy.

* Push proxy (push-notifications.nextcloud.com)
  - Knowledge:
    + user public key (generated by Nextcloud server, send by mobile device)
    + device identifier (generated by Nextcloud server, send by mobile device)
    + push token (generated by mobile device)
    + Google and Apple Developer certificate (generated by Nextcloud)
  - Actions:
    + Verifies the signature of the push notification with `user public key` (based on `device identifier`).
    + Signs the notification with Google or Apple Developer certificate.
    + Forwards to Firebase Cloud Messaging (Google) or Apple Push Notification Service.

* Firebase Cloud Messaging (Google) and Apple Push Notification Service
  - Knowledge:
    + Google and Apple Developer certificate (generated by Nextcloud)
    + push token (generated by mobile device)
  - Actions:
    + Verifies the developer certificate.
    + Forwards the notification to the mobile client.
    + *Note:* Since the notification comes from the Push proxy, Google and Apple don't even know the Nextcloud server sending the notification.

* Mobile device
  - Knowledge:
    + device public and private key (generated by mobile device)
    + user public key (generated by mobile device)
  - Actions:
    + Verifies the signature with `user public key` to make sure the notification is from a known Nextcloud server and account.
    + Decrypts the notification with `device private key`.


## Checking the capabilities of the Nextcloud server

In order to find out if notifications support push on the server you can run a request against the capabilities endpoint: `/ocs/v2.php/cloud/capabilities`

```
{
  "ocs": {
    ...
    "data": {
      ...
      "capabilities": {
        ...
        "notifications": {
          "push": [
            ...
            "devices",
            "object-data",
            "delete"
          ]
        }
      }
    }
  }
}
```



## Subscribing at the Nextcloud server

1. **Only on first registration on the server** The device generates a `rsa2048` key pair (`devicePrivateKey` and `devicePublicKey`).

2. The device generates the `PushToken` for *Apple Push Notification Service* (iOS) or *Firebase Cloud Messaging* (Android)

3. The device generates a `sha512` hash of the `PushToken` (`PushTokenHash`)

4. The device then sends the `devicePublicKey`, `PushTokenHash` and `proxyServerUrl` to the Nextcloud server:

   ```
   POST /ocs/v2.php/apps/notifications/api/v2/push

   {
     "pushTokenHash": "{{PushTokenHash}}",
     "devicePublicKey": "{{devicePublicKey}}",
     "proxyServer": "{{proxyServerUrl}}"
   }
   ```

   ​

### Response

The server replies with the following status codes:

| Status code | Meaning                                                                                                                                            |
|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
| 200         | No further action by the device required                                                                                                           |
| 201         | Push token was created/updated and **needs to be sent to the `Proxy`**                                                                             |
| 400         | Invalid device public key; device does not use a token to authenticate; the push token hash is invalid formatted; the proxy server URL is invalid; |
| 401         | Device is not logged in                                                                                                                            |



#### Body in case of success

In case of `200` and `201` the reply has more information in the body:

| Key              | Type         |                                                        |
|------------------|--------------|--------------------------------------------------------|
| publicKey        | string (512) | rsa2048 public key of the user account on the instance |
| deviceIdentifier | string (128) | unique identifier encrypted with the users private key |
| signature        | string (512) | base64 encoded signature of the deviceIdentifier       |



#### Body in case of an error

In case of `400` the following `message` can appear in the body:

| Error                    | Description                                                                                                  |
|--------------------------|--------------------------------------------------------------------------------------------------------------|
| `INVALID_PUSHTOKEN_HASH` | The hash of the push token was not a valid `sha512` hash.                                                    |
| `INVALID_SESSION_TOKEN`  | The authentication token of the request could not be identified. Check whether a password was used to login. |
| `INVALID_DEVICE_KEY`     | The device key does not match the one registered to the provided session token.                              |
| `INVALID_PROXY_SERVER`   | The proxy server was not a valid https URL.                                                                  |



## Unsubscribing at the Nextcloud server

When an account is removed from a device, the device should unregister on the server. Otherwise the server sends unnecessary push notifications and might be blocked because of spam.



The device should then send a `DELETE` request to the Nextcloud server:

```
DELETE /ocs/v2.php/apps/notifications/api/v2/push
```



### Response

The server replies with the following status codes:

| Status code | Meaning                                                             |
|-------------|---------------------------------------------------------------------|
| 200         | Push token was not registered on the server                         |
| 202         | Push token was deleted and **needs to be deleted from the `Proxy`** |
| 400         | Device does not use a token to authenticate                         |
| 401         | Device is not logged in                                             |



#### Body in case of an error

In case of `400` the following `message` can appear in the body:

| Error                   | Description                                                      |
|-------------------------|------------------------------------------------------------------|
| `INVALID_SESSION_TOKEN` | The authentication token of the request could not be identified. |



## Subscribing at the Push Proxy

The device sends the`PushToken` as well as the `deviceIdentifier`, `signature` and the user´s `publicKey`  (from the server´s response) to the Push Proxy:

```
POST /devices

{
  "pushToken": "{{PushToken}}",
  "deviceIdentifier": "{{deviceIdentifier}}",
  "deviceIdentifierSignature": "{{signature}}",
  "userPublicKey": "{{userPublicKey}}"
}
```



### Response

The server replies with the following status codes:

| Status code | Meaning                                                                                                                                                                                                                                  |
|-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 200         | Push token was written to the database                                                                                                                                                                                                   |
| 400         | Push token, public key or device identifier is malformed, the signature does not match                                                                                                                                                   |
| 403         | Device is not allowed to write the push token of the device identifier                                                                                                                                                                   |
| 409         | In case of a conflict the device can retry with the additional field `cloudId` with the value `{{userid}}@{{serverurl}}` which allows the proxy to verify the public key and device identifier belongs to the given user on the instance |



## Unsubscribing at the Push Proxy

The device sends the `deviceIdentifier`, `deviceIdentifierSignature` and the user´s `publicKey`  (from the server´s response) to the Push Proxy:

```
DELETE /devices

{
  "deviceIdentifier": "{{deviceIdentifier}}",
  "deviceIdentifierSignature": "{{signature}}",
  "userPublicKey": "{{userPublicKey}}"
}
```



### Response

The server replies with the following status codes:

| Status code | Meaning                                                                    |
|-------------|----------------------------------------------------------------------------|
| 200         | Push token was deleted from the database                                   |
| 400         | Public key or device identifier is malformed                               |
| 403         | Device identifier and device public key didn't match or could not be found |



## Pushed notifications

The pushed notifications is defined by the [Firebase Cloud Messaging HTTP Protocol](https://firebase.google.com/docs/cloud-messaging/http-server-ref#send-downstream). The sample content of a Nextcloud push notification looks like the following:

```json
{
  "to" : "APA91bHun4MxP5egoKMwt2KZFBaFUH-1RYqx...",
  "notification" : {
    "body" : "NEW_NOTIFICATION",
    "body_loc_key" : "NEW_NOTIFICATION",
    "title" : "NEW_NOTIFICATION",
    "title_loc_key" : "NEW_NOTIFICATION"
  },
  "data" : {
    "subject" : "*Encrypted subject*",
    "signature" : "*Signature*"
  }
}
```

| Attribute   | Meaning                                                                                      |
|-------------|----------------------------------------------------------------------------------------------|
| `subject`   | The subject is encrypted with the device´s *public key*.                                     |
| `signature` | The signature is a sha512 signature over the encrypted subject using the user´s private key. |

### Encrypted subject data

#### Normal content notification

If you are missing any information necessary to parse the notification in a more usable way, use the `nid` to get the full notification information via [OCS API](ocs-endpoint-v2.md)

```json
{
  "app" : "spreed",
  "subject" : "Test mentioned you in a private conversation",
  "type" : "chat",
  "id" : "t0k3n",
  "nid" : 1337
}
```

| Attribute | Meaning                                                                                                       | Capability    |
|-----------|---------------------------------------------------------------------------------------------------------------|---------------|
| `app`     | The nextcloud app sending the notification                                                                    | -             |
| `subject` | The subject of the actual notification                                                                        | -             |
| `type`    | Type of the object this notification is about                                                                 | `object-data` |
| `id`      | Identifier of the object this notification is about                                                           | `object-data` |
| `nid`     | Numeric identifier of the notification in order to get more information via the [OCS API](ocs-endpoint-v2.md) | `object-data` |


#### Silent delete notification (single)

These notifications should not be shown to the user. Instead you should delete pending system notifications for the respective id

```json
{
  "delete" : true,
  "nid" : 1337
}
```

| Attribute | Meaning                                                                                                       | Capability    |
|-----------|---------------------------------------------------------------------------------------------------------------|---------------|
| `nid`     | Numeric identifier of the notification in order to get more information via the [OCS API](ocs-endpoint-v2.md) | `object-data` |
| `delete`  | Delete all notifications related to `nid`                                                                     | `delete`      |


#### Silent delete notifications (multiple)

These notifications should not be shown to the user. Instead you should delete pending system notifications for the respective ids

```json
{
  "delete-multiple" : true,
  "nids" : [1337]
}
```

| Attribute         | Meaning                                                                                                         | Capability    |
|-------------------|-----------------------------------------------------------------------------------------------------------------|---------------|
| `nids`            | Numeric identifiers of the notifications in order to get more information via the [OCS API](ocs-endpoint-v2.md) | `object-data` |
| `delete-multiple` | Delete all notifications related to `nids`                                                                      | `delete`      |

#### Silent delete notification (all)

These notifications should not be shown to the user. Instead you should delete all pending system notifications for this account

```json
{
  "delete-all" : true
}
```

| Attribute    | Meaning                                          | Capability |
|--------------|--------------------------------------------------|------------|
| `delete-all` | Delete all notifications related to this account | `delete`   |


### Verification
So a device should verify the signature using the user´s public key.
If the signature is okay, the subject can be decrypted using the device´s private key.
