github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/docs/remote.md (about) 1 [Table of contents](README.md#table-of-contents) 2 3 # Proxy for remote data/API 4 5 The client side applications in Cozy are constrained and cannot speak with 6 external websites to avoid leaking personal data. Technically, it is made with 7 the Content Security Policy. These rules are very strict and it would be a pity 8 to not allow a client side app to load public informations from a source like 9 Wikipedia. Our proposal is to make client side apps able to query external 10 websites of their choices, but these requests will be made via the cozy-stack 11 (as a proxy) and will be logged to check later that no personal data was leaked 12 unintentionally. We can see the data loaded from the external website as a 13 document with a doctype: it's just not a doctype local to our cozy, but a remote 14 one. 15 16 A client side app can request data from external websites by doing these three 17 steps: 18 19 1. Declaring a remote doctype and its requests 20 2. Declaring permissions on these doctypes in the manifest 21 3. Calling the `/remote` API of cozy-stack 22 23 ## Declaring a remote doctype 24 25 Doctypes are formalized in this repository: 26 [github.com/cozy/cozy-doctypes](https://github.com/cozy/cozy-doctypes). Each 27 doctype has its own directory inside the repository. For a remote doctype, it 28 will include a file called `request` that will describe how the cozy-stack will 29 request the external website. 30 31 Let's take an example: 32 33 ``` 34 $ tree cozy-doctypes 35 cozy-doctypes 36 ├── [...] 37 ├── org.wikidata.entity 38 │ └── request 39 └── org.wikidata.search 40 └── request 41 42 $ cat cozy-doctypes/org.wikidata.entity/request 43 GET https://www.wikidata.org/wiki/Special:EntityData/{{entity}}.json 44 Accept: application/json 45 46 $ cat cozy-doctypes/org.wikidata.search/request 47 GET https://www.wikidata.org/w/api.php?action=wbsearchentities&search={{q}}&language=en&format=json 48 ``` 49 50 Here, we have two remote doctypes. Each one has a request defined for it. 51 52 The format for the request file is: 53 54 - the verb and the URL on the first line 55 - then some lines that describe the HTTP headers 56 - then a blank line and the body if the request is a POST 57 58 The URL can only use the default port. But, for development and testing in 59 local, this rule can be disabled with the `--remote-allow-custom-port` flag 60 of the `cozy-stack serve` command. 61 62 For the path, the query-string, the headers, and the body, it's possible to have 63 some dynamic part by using `{{`, a variable name, and `}}`. 64 65 Some templating helpers are available to escape specific variables using `{{` 66 function name - space - variable name `}}`. These helpers are only available for 67 the body part of the template. 68 69 Available helpers: 70 71 - `json`: for json parts (`{ "key": "{{json val}}" }`) 72 - `html`: for html parts (`<p>{{html val}}</p>`) 73 - `query`: for query parameter of a url (`http://foobar.com?q={{query q}}`) 74 - `path`: for path component of a url (`http://foobar.com/{{path p}}`) 75 76 Values injected in the URL are automatically URI-escaped based on the part they 77 are included in: namely as a query parameter or as a path component. 78 79 **Note**: by default, the User-Agent is set to a default value ("cozy-stack" and 80 a version number). It can be overriden in the request description. 81 82 Example: 83 84 ``` 85 GET https://foobar.com/{{path}}?q={{query}} 86 Content-Type: {{contentType}} 87 88 { 89 "key": "{{json value}}", 90 "url": "http://anotherurl.com/{{path anotherPath}}?q={{query anotherQuery}}", 91 } 92 ``` 93 94 ## Declaring permissions 95 96 Nothing special here. The client side app must declare that it will use these 97 doctypes in its manifest, like for other doctypes: 98 99 ```json 100 { 101 "...": "...", 102 "permissions": { 103 "search": { 104 "description": "Required for searching on wikidata", 105 "type": "org.wikidata.search" 106 }, 107 "entity": { 108 "description": "Required for getting more informations about an entity on wikidata", 109 "type": "org.wikidata.entity" 110 } 111 } 112 } 113 ``` 114 115 ## Calling the remote API 116 117 ### GET/POST `/remote/:doctype` 118 119 The client side app must use the same verb as the defined request (`GET` in our 120 two previous examples). It can use the query-string for GET, and a JSON body for 121 POST, to give values for the variables. 122 123 Example: 124 125 ```http 126 GET /remote/org.wikidata.search?q=Douglas+Adams HTTP/1.1 127 Host: alice.cozy.localhost 128 ``` 129 130 It is possible to send some extra informations, to make it easier to understand 131 the request. If no variable in the request matches it, it won't be send to the 132 remote website. 133 134 Example: 135 136 ```http 137 POST /remote/org.example HTTP/1.1 138 Host: alice.cozy.localhost 139 Content-Type: application/json 140 ``` 141 142 ```json 143 { 144 "query": "Qbhtynf Nqnzf", 145 "comment": "query is rot13 for Douglas Adams" 146 } 147 ``` 148 149 **Note**: currently, only the response with a content-type that is an image, 150 JSON or XML are accepted. Other content-types are blocked the time to evaluate 151 if they are useful and their security implication (javascript is probably not 152 something we want to allow). 153 154 ### GET `/remote/_all_doctypes` 155 156 This endpoint lists all the known remote doctypes. A permission on 157 `io.cozy.doctypes` for `GET` is needed to query this endoint. 158 159 Example: 160 161 ```http 162 GET /remote/_all_doctypes HTTP/1.1 163 ``` 164 165 ```http 166 HTTP/1.1 200 OK 167 Content-Type: application/json 168 ``` 169 170 ```json 171 ["cc.cozycloud.dacc", "cc.cozycloud.errors", "org.wikidata.entity", "org.wikidata.search"] 172 ``` 173 174 ### GET `/remote/assets/:asset-name` 175 176 The client application can fetch a list of predefined assets via this route. The 177 resources available are defined in the configuration file. 178 179 Example: 180 181 ```http 182 GET /remote/assets/bank HTTP/1.1 183 Host: alice.cozy.localhost 184 ``` 185 186 ## Logs 187 188 The requests are logged as the `io.cozy.remote.requests` doctype, with the 189 doctype asked, the parameter (even those that have not been used, like `comment` 190 in the previous example), and the application that has made the request. 191 192 ## Secrets 193 194 It is possible to make the stack inject a secret in a request. 195 196 For example, if we want to make the stack inject a `token` for 197 the request, then: 198 199 1- We need to store the secret in CouchDB, in the 200 `secrets/io-cozy-remote-secrets` database. The document must 201 have the remote doctype as an id, and the secret in another field, 202 like this: 203 204 ```json 205 { 206 "_id": "cc.cozycloud.foobar", 207 "token": "1c7f3ba03bd801391a91543d7eb8149c" 208 } 209 ``` 210 211 2- Use this secret in the remote doctype declaration: 212 213 ```http 214 GET https://foobar.com/baz/ HTTP/1.1 215 Authorization: Bearer {{secret_token}} 216 ``` 217 218 You can see that we store `token` in the `io-cozy-remote-secret` 219 but we use `secret_token` in the remote doctype declaration, this 220 is a convention to follow. Like that, we quickly know what come 221 from secrets and what come from the caller. 222 223 If you use secret and you get a 400 Bad Request error 224 `a variable is used in the template, but no value was given` 225 check if you follow this convention. 226 227 ## For developers 228 229 If you are a developer and you want to use a new remote doctype, it can be 230 difficult to first make it available in the github.com/cozy/cozy-doctypes 231 repository and only then test it. So, the cozy-stack serve command has a 232 `--doctypes` option to gives a local directory with the doctypes. You can fork 233 the repository, clone it, work on a new doctype inside, test it locally, and 234 when OK, make a pull request for it.