├── .github └── first-timers.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── apiary.apib ├── package.json └── scripts └── summary.js /.github/first-timers.yml: -------------------------------------------------------------------------------- 1 | repository: camp 2 | labels: 3 | - first-timers-only 4 | - hacktoberfest 5 | - available 6 | template: .github/FIRST_TIMERS_ISSUE_TEMPLATE.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - ~/.npm 5 | node_js: 6 | - lts/* 7 | 8 | # Trigger a push build on master and greenkeeper branches + PRs build on every branches 9 | # Avoid double build on PRs (See https://github.com/travis-ci/travis-ci/issues/1147) 10 | branches: 11 | only: 12 | - master 13 | - /^greenkeeper.*$/ 14 | 15 | jobs: 16 | include: 17 | - script: npm run test 18 | - stage: release 19 | node_js: lts/* 20 | script: 21 | - npm run semantic-release 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2012-2016 the Hoodie Community and other contributors. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # account-json-api 2 | 3 | > RESTful JSON API-blueprint for all things user accounts 4 | 5 | [![NPM version](https://badge.fury.io/js/account-json-api.svg)](https://www.npmjs.com/package/account-json-api) 6 | [![Build Status](https://travis-ci.org/hoodiehq/account-json-api.svg?branch=master)](https://travis-ci.org/hoodiehq/account-json-api) 7 | 8 | This is a [JSON API v1.0](http://jsonapi.org/format/) compliant specification 9 | for a generic REST API for all things user accounts. 10 | 11 | ## Summary 12 | 13 | ``` 14 | PUT /session 15 | GET /session 16 | DELETE /session 17 | PUT /session/account 18 | GET /session/account 19 | PATCH /session/account 20 | DELETE /session/account 21 | GET /session/account/profile 22 | PATCH /session/account/profile 23 | 24 | # Password resets, upgrades, etc. 25 | GET /requests 26 | POST /requests 27 | GET /requests/{id} 28 | DELETE /requests/{id} 29 | 30 | # Admin only 31 | POST /accounts 32 | GET /accounts 33 | GET /accounts/{id} 34 | PATCH /accounts/{id} 35 | DELETE /accounts/{id} 36 | GET /accounts/{id}/profile 37 | PATCH /accounts/{id}/profile 38 | POST /accounts/{id}/sessions 39 | ``` 40 | 41 | Find the full spec at http://docs.accountjsonapi.apiary.io 42 | 43 | ## Example 44 | 45 | Request 46 | 47 | ``` 48 | PUT /session 49 | Content-Type: application/vnd.api+json 50 | Accept: application/vnd.api+json 51 | { 52 | "data": { 53 | "type": "session", 54 | "attributes": { 55 | "username": "john-doe", 56 | "password": "secret" 57 | } 58 | } 59 | } 60 | ``` 61 | 62 | Response 63 | 64 | ``` 65 | { 66 | "links": { 67 | "self": "https://example.com/session" 68 | }, 69 | "data": { 70 | "id": "sessionid123", 71 | "type": "session", 72 | "relationships": { 73 | "account": { 74 | "links": { 75 | "related": "https://example.com/session/account" 76 | }, 77 | "data": { 78 | "id": "abc1234", 79 | "type": "account" 80 | } 81 | } 82 | } 83 | }, 84 | "included": [{ 85 | "id": "abc1234", 86 | "type": "account", 87 | "attributes": { 88 | "username": "jane-doe" 89 | }, 90 | "relationships": { 91 | "profile": { 92 | "links": { 93 | "related": "https://example.com/session/account/profile" 94 | }, 95 | "data": { 96 | "id": "abc1234-profile", 97 | "type": "accountProfile" 98 | } 99 | } 100 | } 101 | }] 102 | } 103 | ``` 104 | 105 | ## Implementations 106 | 107 | The `account-json-api` is currently being implemented by [Hoodie](http://hood.ie) 108 | in the [hoodie-server-account](https://github.com/hoodiehq/hoodie-server-account) 109 | module. 110 | 111 | ## License 112 | 113 | MIT 114 | -------------------------------------------------------------------------------- /apiary.apib: -------------------------------------------------------------------------------- 1 | FORMAT: 1A 2 | 3 | # account-json-api 4 | 5 | An implementation of a [JSON-API v1](http://jsonapi.org/) RESTful interface for 6 | all things user accounts and sessions. 7 | 8 | Send your pull-requests to https://github.com/hoodiehq/account-json-api 9 | 10 | ## Query parameters 11 | 12 | All routes support query parameters to refine responses 13 | 14 | - **[include](http://jsonapi.org/format/#fetching-relationships)**, 15 | e.g. `GET /session?include=account.profile` to include properties of the 16 | profile relationship 17 | - **[fields](http://jsonapi.org/format/#fetching-sparse-fieldsets)**, 18 | e.g. `GET /session?fields[account]=username` to only return the username 19 | of the account relationship 20 | - **[sort](http://jsonapi.org/format/#fetching-sorting)**, 21 | e.g. `GET /accounts?sort=username` to sort all accounts ascending by name 22 | - **[page](http://jsonapi.org/format/#fetching-pagination)**, 23 | e.g. `GET /accounts?page[number]=1&page[size]=10` to return only the first 24 | 10 accounts 25 | - **[filter](http://jsonapi.org/format/#fetching-filtering)**, 26 | The `filter` is not defined by JSON API. Implementations must specify if how 27 | the `filter` query parameter is supported. 28 | 29 | ## Group Current User 30 | 31 | ## Session [/session] 32 | 33 | The session resource allows users to sign in, sign out and to check their 34 | current session status. 35 | 36 | A user can have several open sessions at the same time. 37 | 38 | If the session identified by the Authorization header can't be found, a 39 | `401` error is returned (see discussion about error status codes in 40 | https://twitter.com/gr2m/status/697804572623007744) 41 | 42 | ### Sign In [PUT] 43 | 44 | + Request (application/vnd.api+json) 45 | 46 | + Headers 47 | 48 | Accept: application/vnd.api+json 49 | 50 | + Body 51 | 52 | { 53 | "data": { 54 | "type": "session", 55 | "attributes": { 56 | "username": "john-doe", 57 | "password": "secret" 58 | } 59 | } 60 | } 61 | 62 | + Response 201 (application/vnd.api+json) 63 | 64 | + Headers 65 | 66 | Location: https://example.com/session 67 | 68 | + Body 69 | 70 | { 71 | "links": { 72 | "self": "https://example.com/session" 73 | }, 74 | "data": { 75 | "id": "sessionid123", 76 | "type": "session", 77 | "relationships": { 78 | "account": { 79 | "links": { 80 | "related": "https://example.com/session/account" 81 | }, 82 | "data": { 83 | "id": "abc1234", 84 | "type": "account" 85 | } 86 | } 87 | } 88 | }, 89 | "included": [{ 90 | "id": "abc1234", 91 | "type": "account", 92 | "attributes": { 93 | "username": "jane-doe" 94 | }, 95 | "relationships": { 96 | "profile": { 97 | "links": { 98 | "related": "https://example.com/session/account/profile" 99 | }, 100 | "data": { 101 | "id": "abc1234-profile", 102 | "type": "profile" 103 | } 104 | } 105 | } 106 | }] 107 | } 108 | 109 | + Response 401 (application/vnd.api+json) 110 | 111 | { 112 | errors: [{ 113 | "status": "401", 114 | "title": "Unauthorized", 115 | "detail": "Invalid credentials" 116 | }] 117 | } 118 | 119 | + Response 409 (application/vnd.api+json) 120 | 121 | { 122 | errors: [{ 123 | "status": "409", 124 | "title": "Conflict", 125 | "detail": "data.type must be 'session'" 126 | }] 127 | } 128 | 129 | + Response 403 (application/vnd.api+json) 130 | 131 | { 132 | errors: [{ 133 | "status": "403", 134 | "title": "Forbidden", 135 | "detail": "Authorization header not allowed" 136 | }] 137 | } 138 | 139 | ### Check session [GET] 140 | 141 | + Request 142 | 143 | + Headers 144 | 145 | Accept: application/vnd.api+json 146 | Authorization: Bearer sessionid123 147 | 148 | + Response 200 (application/vnd.api+json) 149 | 150 | { 151 | "links": { 152 | "self": "https://example.com/session" 153 | }, 154 | "data": { 155 | "id": "sessionid123", 156 | "type": "session", 157 | "relationships": { 158 | "account": { 159 | "links": { 160 | "related": "https://example.com/session/account" 161 | }, 162 | "data": { 163 | "id": "abc1234", 164 | "type": "account" 165 | } 166 | } 167 | } 168 | }, 169 | "included": [{ 170 | "id": "abc1234", 171 | "type": "account", 172 | "attributes": { 173 | "username": "jane-doe" 174 | }, 175 | "relationships": { 176 | "profile": { 177 | "links": { 178 | "related": "https://example.com/session/account/profile" 179 | }, 180 | "data": { 181 | "id": "abc1234-profile", 182 | "type": "profile" 183 | } 184 | } 185 | } 186 | }] 187 | } 188 | 189 | + Response 401 (application/vnd.api+json) 190 | 191 | { 192 | errors: [{ 193 | "status": "401", 194 | "title": "Unauthorized", 195 | "detail": "Authorization header missing" 196 | }] 197 | } 198 | 199 | + Response 401 (application/vnd.api+json) 200 | 201 | { 202 | errors: [{ 203 | "status": "401", 204 | "title": "Unauthorized", 205 | "detail": "Session invalid" 206 | }] 207 | } 208 | 209 | ### Sign Out [DELETE] 210 | 211 | + Request 212 | 213 | + Headers 214 | 215 | Accept: application/vnd.api+json 216 | Authorization: Bearer sessionid123 217 | 218 | + Response 204 219 | 220 | + Response 401 (application/vnd.api+json) 221 | 222 | { 223 | errors: [{ 224 | "status": "401", 225 | "title": "Unauthorized", 226 | "detail": "Authorization header missing" 227 | }] 228 | } 229 | 230 | + Response 401 (application/vnd.api+json) 231 | 232 | { 233 | errors: [{ 234 | "status": "401", 235 | "title": "Unauthorized", 236 | "detail": "Session invalid" 237 | }] 238 | } 239 | 240 | ## Account [/session/account] 241 | 242 | The User account resource allows users to sign up, get 243 | or set account properties or destroy their accounts. 244 | 245 | ### Sign Up [PUT /session/account] 246 | 247 | + Request (application/vnd.api+json) 248 | 249 | + Headers 250 | 251 | Accept: application/vnd.api+json 252 | 253 | + Attributes (object) 254 | + data (object, required) 255 | + type (string, required) 256 | + attributes (object, required) - may contain the properties below as well as any others that should be included 257 | + username (string) 258 | + password (string) 259 | + id (string) - the server may accept a client-generated id along with the request 260 | 261 | + Body 262 | 263 | { 264 | "data": { 265 | "type": "account", 266 | "id": "abc4567" 267 | "attributes": { 268 | "username": "john-doe", 269 | "password": "secret", 270 | "createdAt": "2016-10-02T04:07:20.243Z" 271 | } 272 | } 273 | } 274 | 275 | + Response 201 (application/vnd.api+json) 276 | 277 | + Headers 278 | 279 | Location: https://example.com/session/account 280 | 281 | + Body 282 | 283 | { 284 | "links": { 285 | "self": "https://example.com/session/account" 286 | }, 287 | "data": { 288 | "id": "abc4567", 289 | "type": "account", 290 | "attributes": { 291 | "username": "john-doe" 292 | }, 293 | "relationships": { 294 | "profile": { 295 | "links": { 296 | "related": "https://example.com/session/account/profile" 297 | }, 298 | "data": { 299 | "id": "abc4567-profile", 300 | "type": "profile" 301 | } 302 | } 303 | } 304 | } 305 | } 306 | 307 | + Response 403 (application/vnd.api+json) 308 | 309 | { 310 | errors: [{ 311 | "status": "403", 312 | "title": "Forbidden", 313 | "detail": "Authorization header not allowed" 314 | }] 315 | } 316 | 317 | + Response 409 (application/vnd.api+json) 318 | 319 | { 320 | errors: [{ 321 | "status": "409", 322 | "title": "Conflict", 323 | "detail": "An account with that username already exists" 324 | }] 325 | } 326 | 327 | + Response 409 (application/vnd.api+json) 328 | 329 | { 330 | errors: [{ 331 | "status": "409", 332 | "title": "Conflict", 333 | "detail": "data.type must be 'account'" 334 | }] 335 | } 336 | 337 | ### Fetch [GET /session/account] 338 | 339 | + Request 340 | 341 | + Headers 342 | 343 | Accept: application/vnd.api+json 344 | Authorization: Bearer sessionid123 345 | 346 | + Response 200 (application/vnd.api+json) 347 | 348 | { 349 | "links": { 350 | "self": "https://example.com/session/account" 351 | }, 352 | "data": { 353 | "id": "abc4567", 354 | "type": "account", 355 | "attributes": { 356 | "username": "john-doe" 357 | }, 358 | "relationships": { 359 | "profile": { 360 | "links": { 361 | "related": "https://example.com/session/account/profile" 362 | }, 363 | "data": { 364 | "id": "abc4567-profile", 365 | "type": "profile" 366 | } 367 | } 368 | } 369 | } 370 | } 371 | 372 | + Response 401 (application/vnd.api+json) 373 | 374 | { 375 | errors: [{ 376 | "status": "401", 377 | "title": "Unauthorized", 378 | "detail": "Authorization header missing" 379 | }] 380 | } 381 | 382 | + Response 401 (application/vnd.api+json) 383 | 384 | { 385 | errors: [{ 386 | "status": "401", 387 | "title": "Unauthorized", 388 | "detail": "Session invalid" 389 | }] 390 | } 391 | 392 | ### Update [PATCH] 393 | 394 | + Request (application/vnd.api+json) 395 | 396 | + Headers 397 | 398 | Accept: application/vnd.api+json 399 | Authorization: Bearer sessionid123 400 | 401 | + Body 402 | 403 | { 404 | "data": { 405 | "id": "def678", 406 | "type": "account", 407 | "attributes": { 408 | "password": "newsecret" 409 | } 410 | } 411 | } 412 | 413 | + Response 204 414 | 415 | + Response 401 (application/vnd.api+json) 416 | 417 | { 418 | errors: [{ 419 | "status": "401", 420 | "title": "Unauthorized", 421 | "detail": "Authorization header missing" 422 | }] 423 | } 424 | 425 | + Response 401 (application/vnd.api+json) 426 | 427 | { 428 | errors: [{ 429 | "status": "401", 430 | "title": "Unauthorized", 431 | "detail": "Session invalid" 432 | }] 433 | } 434 | 435 | + Response 409 (application/vnd.api+json) 436 | 437 | { 438 | errors: [{ 439 | "status": "409", 440 | "title": "Conflict", 441 | "detail": "data.type must be 'account'" 442 | }] 443 | } 444 | 445 | + Response 409 (application/vnd.api+json) 446 | 447 | { 448 | errors: [{ 449 | "status": "409", 450 | "title": "Conflict", 451 | "detail": "data.id must be '{id}'" 452 | }] 453 | } 454 | 455 | ### Close [DELETE] 456 | 457 | + Request 458 | 459 | + Headers 460 | 461 | Accept: application/vnd.api+json 462 | Authorization: Bearer sessionid123 463 | 464 | + Response 204 465 | 466 | + Response 401 (application/vnd.api+json) 467 | 468 | { 469 | errors: [{ 470 | "status": "401", 471 | "title": "Unauthorized", 472 | "detail": "Authorization header missing" 473 | }] 474 | } 475 | 476 | + Response 401 (application/vnd.api+json) 477 | 478 | { 479 | errors: [{ 480 | "status": "401", 481 | "title": "Unauthorized", 482 | "detail": "Session invalid" 483 | }] 484 | } 485 | 486 | 487 | ## Profile [/session/account/profile] 488 | 489 | All custom user data is stored in the profile. 490 | 491 | Every account has an associated profile, by default, an empty one 492 | is created when creating the account. 493 | 494 | ### Fetch [GET] 495 | 496 | + Request 497 | 498 | + Headers 499 | 500 | Accept: application/vnd.api+json 501 | Authorization: Bearer sessionid123 502 | 503 | + Response 200 (application/vnd.api+json) 504 | 505 | { 506 | "links": { 507 | "self": "https://example.com/session/account/profile" 508 | }, 509 | "data": { 510 | "id": "abc123-profile", 511 | "type": "profile", 512 | "attributes": { 513 | "fullName": "Jane Doe" 514 | } 515 | } 516 | } 517 | 518 | + Response 401 (application/vnd.api+json) 519 | 520 | { 521 | errors: [{ 522 | "status": "401", 523 | "title": "Unauthorized", 524 | "detail": "Authorization header missing" 525 | }] 526 | } 527 | 528 | + Response 401 (application/vnd.api+json) 529 | 530 | { 531 | errors: [{ 532 | "status": "401", 533 | "title": "Unauthorized", 534 | "detail": "Session invalid" 535 | }] 536 | } 537 | 538 | ### Update [PATCH] 539 | 540 | + Request (application/vnd.api+json) 541 | 542 | + Headers 543 | 544 | Accept: application/vnd.api+json 545 | Authorization: Bearer sessionid123 546 | 547 | + Body 548 | 549 | { 550 | "data": { 551 | "id": "abc123-profile", 552 | "type": "profile", 553 | "attributes": { 554 | "property": "new value" 555 | } 556 | } 557 | } 558 | 559 | + Response 204 560 | 561 | + Response 401 (application/vnd.api+json) 562 | 563 | { 564 | errors: [{ 565 | "status": "401", 566 | "title": "Unauthorized", 567 | "detail": "Authorization header missing" 568 | }] 569 | } 570 | 571 | + Response 401 (application/vnd.api+json) 572 | 573 | { 574 | errors: [{ 575 | "status": "401", 576 | "title": "Unauthorized", 577 | "detail": "Session invalid" 578 | }] 579 | } 580 | 581 | + Response 409 (application/vnd.api+json) 582 | 583 | { 584 | errors: [{ 585 | "status": "409", 586 | "title": "Conflict", 587 | "detail": "'type' and 'id' provided don't match any existing document" 588 | }] 589 | } 590 | 591 | ## Group Requests 592 | 593 | 594 | Requests can be used for things like account confirmation, password resets 595 | or account ugrades, which require app-specific workflows. 596 | 597 | Authentication might or might not be needed, depending on the type of request. 598 | 599 | ## Request Collection [/requests] 600 | 601 | A user can only see own requests. An admin can see all requests across accounts. 602 | 603 | ### List all [GET] 604 | 605 | + Request 606 | 607 | + Headers 608 | 609 | Accept: application/vnd.api+json 610 | Authorization: Bearer sessionid123 611 | 612 | + Response 200 (application/vnd.api+json) 613 | 614 | { 615 | "links": { 616 | "self": "https://example.com/requests", 617 | "first": "https://example.com/requests", 618 | "last": "https://example.com/requests?page=2", 619 | "next": "https://example.com/requests?page=2" 620 | }, 621 | "data": [ 622 | { 623 | "id": "1234", 624 | "type": "request", 625 | "links": { 626 | "self": "https://example.com/requests/1234" 627 | }, 628 | "attributes": { 629 | "type": "passwordreset", 630 | "email": "john@example.com", 631 | "expires": "2015-07-20T10:00:00-04:00" 632 | } 633 | }, 634 | { 635 | "id": "2345", 636 | "type": "request", 637 | "links": { 638 | "self": "https://example.com/requests/2345" 639 | }, 640 | "attributes": { 641 | "type": "usernamereminder", 642 | "email": "jane@example.com", 643 | "expires": "2015-07-20T15:00:00-04:00" 644 | } 645 | } 646 | ] 647 | } 648 | 649 | + Response 401 (application/vnd.api+json) 650 | 651 | { 652 | errors: [{ 653 | "status": "401", 654 | "title": "Unauthorized", 655 | "detail": "Authorization header missing" 656 | }] 657 | } 658 | 659 | + Response 401 (application/vnd.api+json) 660 | 661 | { 662 | errors: [{ 663 | "status": "401", 664 | "title": "Unauthorized", 665 | "detail": "Session invalid" 666 | }] 667 | } 668 | 669 | ### Create [POST] 670 | 671 | + Request (application/vnd.api+json) 672 | 673 | + Headers 674 | 675 | Accept: application/vnd.api+json 676 | Authorization: Bearer sessionid123 677 | 678 | + Body 679 | 680 | { 681 | "data": { 682 | "type": "request", 683 | "attributes": { 684 | "type": "passwordreset", 685 | "email": "john@example.com" 686 | } 687 | } 688 | } 689 | 690 | + Response 201 (application/vnd.api+json) 691 | 692 | + Headers 693 | 694 | Location: https://example.com/requests/1234 695 | 696 | + Body 697 | 698 | { 699 | "links": { 700 | "self": "https://example.com/requests/1234" 701 | }, 702 | "data": { 703 | "id": "1234", 704 | "type": "request", 705 | "attributes": { 706 | "type": "passwordreset", 707 | "email": "john@example.com", 708 | "expires": "2015-07-20T15:00:00-04:00" 709 | } 710 | } 711 | } 712 | 713 | + Response 401 (application/vnd.api+json) 714 | 715 | { 716 | errors: [{ 717 | "status": "401", 718 | "title": "Unauthorized", 719 | "detail": "Authorization header missing" 720 | }] 721 | } 722 | 723 | + Response 401 (application/vnd.api+json) 724 | 725 | { 726 | errors: [{ 727 | "status": "401", 728 | "title": "Unauthorized", 729 | "detail": "Session invalid" 730 | }] 731 | } 732 | 733 | + Response 409 (application/vnd.api+json) 734 | 735 | { 736 | errors: [{ 737 | "status": "409", 738 | "title": "Conflict", 739 | "detail": "data.type must be 'request'" 740 | }] 741 | } 742 | 743 | + Response 403 (application/vnd.api+json) 744 | 745 | { 746 | errors: [{ 747 | "status": "403", 748 | "title": "Forbidden", 749 | "detail": "Unknown request type" 750 | }] 751 | } 752 | 753 | 754 | ## Request [/requests/{id}] 755 | 756 | `DELETE` always requires authentication and is restricted to admins. 757 | 758 | `GET` makes sense for users to check for errors that might have occured after 759 | creating a request, like an error during password reset delivery. It might or 760 | might not require authentication, depending on the type of request. 761 | 762 | + Parameters 763 | + id: 1234 (required, string) - request id 764 | 765 | ### Fetch [GET] 766 | 767 | + Request 768 | 769 | + Headers 770 | 771 | Accept: application/vnd.api+json 772 | Authorization: Bearer sessionid123 773 | 774 | + Response 200 (application/vnd.api+json) 775 | 776 | { 777 | "links": { 778 | "self": "https://example.com/requests/1234" 779 | }, 780 | "data": { 781 | "id": "1234", 782 | "type": "request", 783 | "attributes": { 784 | "type": "passwordreset", 785 | "email": "john@example.com", 786 | "expires": "2015-07-20T15:00:00-04:00" 787 | } 788 | } 789 | } 790 | 791 | + Response 401 (application/vnd.api+json) 792 | 793 | { 794 | errors: [{ 795 | "status": "401", 796 | "title": "Unauthorized", 797 | "detail": "Authorization header missing" 798 | }] 799 | } 800 | 801 | + Response 401 (application/vnd.api+json) 802 | 803 | { 804 | errors: [{ 805 | "status": "401", 806 | "title": "Unauthorized", 807 | "detail": "Session invalid" 808 | }] 809 | } 810 | 811 | ### Delete [DELETE] 812 | 813 | + Request 814 | 815 | + Headers 816 | 817 | Accept: application/vnd.api+json 818 | Authorization: Bearer sessionid123 819 | 820 | + Response 204 821 | 822 | + Response 401 (application/vnd.api+json) 823 | 824 | { 825 | errors: [{ 826 | "status": "401", 827 | "title": "Unauthorized", 828 | "detail": "Authorization header missing" 829 | }] 830 | } 831 | 832 | + Response 401 (application/vnd.api+json) 833 | 834 | { 835 | errors: [{ 836 | "status": "401", 837 | "title": "Unauthorized", 838 | "detail": "Session invalid" 839 | }] 840 | } 841 | 842 | ## Group Admins 843 | 844 | 845 | ## User Account Collection [/accounts] 846 | 847 | Admins can fetch and manage all user accounts and their profile properties. 848 | 849 | ### Create [POST] 850 | 851 | + Request (application/vnd.api+json) 852 | 853 | + Headers 854 | 855 | Accept: application/vnd.api+json 856 | Authorization: Bearer sessionid123 857 | 858 | + Body 859 | 860 | { 861 | "data": { 862 | "type": "account", 863 | "attributes": { 864 | "username": "jane-doe", 865 | "password": "secret" 866 | } 867 | } 868 | } 869 | 870 | + Response 201 (application/vnd.api+json) 871 | 872 | + Headers 873 | 874 | Location: https://example.com/accounts/abc4567 875 | 876 | + Body 877 | 878 | { 879 | "links": { 880 | "self": "https://example.com/accounts/abc4567" 881 | }, 882 | "data": { 883 | "id": "abc4567", 884 | "type": "account", 885 | "attributes": { 886 | "username": "jane-doe" 887 | }, 888 | "relationships": { 889 | "profile": { 890 | "links": { 891 | "related": "https://example.com/accounts/abc4567/profile" 892 | }, 893 | "data": { 894 | "id": "abc4567-profile", 895 | "type": "profile", 896 | } 897 | } 898 | } 899 | } 900 | } 901 | 902 | + Response 401 (application/vnd.api+json) 903 | 904 | { 905 | errors: [{ 906 | "status": "401", 907 | "title": "Unauthorized", 908 | "detail": "Authorization header missing" 909 | }] 910 | } 911 | 912 | + Response 401 (application/vnd.api+json) 913 | 914 | { 915 | errors: [{ 916 | "status": "401", 917 | "title": "Unauthorized", 918 | "detail": "Session invalid" 919 | }] 920 | } 921 | 922 | + Response 409 (application/vnd.api+json) 923 | 924 | { 925 | errors: [{ 926 | "status": "409", 927 | "title": "Conflict", 928 | "detail": "data.type must be 'account'" 929 | }] 930 | } 931 | 932 | ### List all [GET] 933 | 934 | + Request 935 | 936 | + Headers 937 | 938 | Accept: application/vnd.api+json 939 | Authorization: Bearer sessionid123 940 | 941 | + Response 200 (application/vnd.api+json) 942 | 943 | { 944 | "links": { 945 | "self": "https://example.com/accounts", 946 | "first": "https://example.com/accounts", 947 | "last": "https://example.com/accounts?page=2", 948 | "next": "https://example.com/accounts?page=2" 949 | }, 950 | "data": [ 951 | { 952 | "id": "abc4567", 953 | "type": "account", 954 | "links": { 955 | "self": "https://example.com/accounts/abc4567" 956 | }, 957 | "attributes": { 958 | "username": "jane-doe" 959 | }, 960 | "relationships": { 961 | "profile": { 962 | "links": { 963 | "related": "https://example.com/accounts/abc4567/profile" 964 | }, 965 | "data": { 966 | "id": "abc4567-profile", 967 | "type": "profile" 968 | } 969 | } 970 | } 971 | }, 972 | { 973 | "id": "def678", 974 | "type": "account", 975 | "links": { 976 | "self": "https://example.com/accounts/def678" 977 | }, 978 | "attributes": { 979 | "username": "john-doe" 980 | }, 981 | "relationships": { 982 | "profile": { 983 | "links": { 984 | "related": "https://example.com/accounts/def678/profile" 985 | }, 986 | "data": { 987 | "id": "def678-profile", 988 | "type": "profile" 989 | } 990 | } 991 | } 992 | } 993 | ] 994 | } 995 | 996 | + Response 401 (application/vnd.api+json) 997 | 998 | { 999 | errors: [{ 1000 | "status": "401", 1001 | "title": "Unauthorized", 1002 | "detail": "Authorization header missing" 1003 | }] 1004 | } 1005 | 1006 | + Response 401 (application/vnd.api+json) 1007 | 1008 | { 1009 | errors: [{ 1010 | "status": "401", 1011 | "title": "Unauthorized", 1012 | "detail": "Session invalid" 1013 | }] 1014 | } 1015 | 1016 | ## User Account [/accounts/{id}] 1017 | 1018 | + Parameters 1019 | 1020 | + id: abc4567 (required, string) - id of account 1021 | 1022 | ### Fetch [GET] 1023 | 1024 | + Request 1025 | 1026 | + Headers 1027 | 1028 | Accept: application/vnd.api+json 1029 | Authorization: Bearer sessionid123 1030 | 1031 | + Response 200 (application/vnd.api+json) 1032 | 1033 | { 1034 | "links": { 1035 | "self": "https://example.com/accounts/def678" 1036 | }, 1037 | "data": { 1038 | "id": "def678", 1039 | "type": "account", 1040 | "attributes": { 1041 | "username": "jane-doe", 1042 | } 1043 | }, 1044 | "relationships": { 1045 | "profile": { 1046 | "links": { 1047 | "related": "https://example.com/accounts/def678/profile" 1048 | }, 1049 | "data": { 1050 | "id": "def678-profile", 1051 | "type": "profile" 1052 | } 1053 | } 1054 | } 1055 | } 1056 | 1057 | + Response 401 (application/vnd.api+json) 1058 | 1059 | { 1060 | errors: [{ 1061 | "status": "401", 1062 | "title": "Unauthorized", 1063 | "detail": "Authorization header missing" 1064 | }] 1065 | } 1066 | 1067 | + Response 401 (application/vnd.api+json) 1068 | 1069 | { 1070 | errors: [{ 1071 | "status": "401", 1072 | "title": "Unauthorized", 1073 | "detail": "Session invalid" 1074 | }] 1075 | } 1076 | 1077 | ### Update [PATCH] 1078 | 1079 | + Request (application/vnd.api+json) 1080 | 1081 | + Headers 1082 | 1083 | Accept: application/vnd.api+json 1084 | Authorization: Bearer sessionid123 1085 | 1086 | + Body 1087 | 1088 | { 1089 | "data": { 1090 | "id": "abc1234", 1091 | "type": "account", 1092 | "attributes": { 1093 | "newProperty": "value", 1094 | "secureProperty": "wicked" 1095 | } 1096 | } 1097 | } 1098 | 1099 | + Response 204 1100 | 1101 | + Response 401 (application/vnd.api+json) 1102 | 1103 | { 1104 | errors: [{ 1105 | "status": "401", 1106 | "title": "Unauthorized", 1107 | "detail": "Authorization header missing" 1108 | }] 1109 | } 1110 | 1111 | + Response 401 (application/vnd.api+json) 1112 | 1113 | { 1114 | errors: [{ 1115 | "status": "401", 1116 | "title": "Unauthorized", 1117 | "detail": "Session invalid" 1118 | }] 1119 | } 1120 | 1121 | + Response 409 (application/vnd.api+json) 1122 | 1123 | { 1124 | errors: [{ 1125 | "status": "409", 1126 | "title": "Conflict", 1127 | "detail": "'type' and 'id' provided don't match any existing document" 1128 | }] 1129 | } 1130 | 1131 | ### Delete [DELETE] 1132 | 1133 | + Request 1134 | 1135 | + Headers 1136 | 1137 | Accept: application/vnd.api+json 1138 | Authorization: Bearer sessionid123 1139 | 1140 | + Response 204 1141 | 1142 | + Response 401 (application/vnd.api+json) 1143 | 1144 | { 1145 | errors: [{ 1146 | "status": "401", 1147 | "title": "Unauthorized", 1148 | "detail": "Authorization header missing" 1149 | }] 1150 | } 1151 | 1152 | + Response 401 (application/vnd.api+json) 1153 | 1154 | { 1155 | errors: [{ 1156 | "status": "401", 1157 | "title": "Unauthorized", 1158 | "detail": "Session invalid" 1159 | }] 1160 | } 1161 | 1162 | ## User Profile [/accounts/{id}/profile] 1163 | 1164 | All custom user data is stored in the profile. 1165 | 1166 | + Parameters 1167 | 1168 | + id: abc4567 (required, string) - id of account 1169 | 1170 | ### Fetch [GET] 1171 | 1172 | + Request 1173 | 1174 | + Headers 1175 | 1176 | Accept: application/vnd.api+json 1177 | Authorization: Bearer sessionid123 1178 | 1179 | + Response 200 (application/vnd.api+json) 1180 | 1181 | { 1182 | "links": { 1183 | "self": "https://example.com/accounts/abcd123/profile" 1184 | }, 1185 | "data": { 1186 | "id": "abcd123-profile", 1187 | "type": "profile", 1188 | "attributes": { 1189 | "fullName": "Jane Doe" 1190 | } 1191 | } 1192 | } 1193 | 1194 | + Response 401 (application/vnd.api+json) 1195 | 1196 | { 1197 | errors: [{ 1198 | "status": "401", 1199 | "title": "Unauthorized", 1200 | "detail": "Authorization header missing" 1201 | }] 1202 | } 1203 | 1204 | + Response 401 (application/vnd.api+json) 1205 | 1206 | { 1207 | errors: [{ 1208 | "status": "401", 1209 | "title": "Unauthorized", 1210 | "detail": "Session invalid" 1211 | }] 1212 | } 1213 | 1214 | ### Update [PATCH] 1215 | 1216 | + Request (application/vnd.api+json) 1217 | 1218 | + Headers 1219 | 1220 | Accept: application/vnd.api+json 1221 | Authorization: Bearer sessionid123 1222 | 1223 | + Body 1224 | 1225 | { 1226 | "data": { 1227 | "id": "abcd123-profile", 1228 | "type": "profile", 1229 | "attributes": { 1230 | "property": "new value" 1231 | } 1232 | } 1233 | } 1234 | 1235 | + Response 204 1236 | 1237 | + Response 401 (application/vnd.api+json) 1238 | 1239 | { 1240 | errors: [{ 1241 | "status": "401", 1242 | "title": "Unauthorized", 1243 | "detail": "Authorization header missing" 1244 | }] 1245 | } 1246 | 1247 | + Response 401 (application/vnd.api+json) 1248 | 1249 | { 1250 | errors: [{ 1251 | "status": "401", 1252 | "title": "Unauthorized", 1253 | "detail": "Session invalid" 1254 | }] 1255 | } 1256 | 1257 | + Response 409 (application/vnd.api+json) 1258 | 1259 | { 1260 | errors: [{ 1261 | "status": "409", 1262 | "title": "Conflict", 1263 | "detail": "'type' and 'id' provided don't match any existing document" 1264 | }] 1265 | } 1266 | 1267 | ## User Session [/accounts/{id}/sessions] 1268 | 1269 | Admins can manage sessions for all user accounts 1270 | 1271 | + Parameters 1272 | 1273 | + id: abc4567 (required, string) - id of account 1274 | 1275 | ### Create [POST] 1276 | 1277 | + Request 1278 | 1279 | + Headers 1280 | 1281 | Accept: application/vnd.api+json 1282 | Authorization: Bearer sessionid123 1283 | 1284 | + Response 201 (application/vnd.api+json) 1285 | 1286 | { 1287 | "links": { 1288 | "self": "https://example.com/accounts/abcd123/sessions/session123" 1289 | }, 1290 | "data": { 1291 | "id": "session123", 1292 | "type": "session", 1293 | "relationships": { 1294 | "account": { 1295 | "links": { 1296 | "related": "https://example.com/accounts/abcd123" 1297 | }, 1298 | "data": { 1299 | "id": "abcd123", 1300 | "type": "account" 1301 | } 1302 | } 1303 | } 1304 | } 1305 | } 1306 | 1307 | + Response 401 (application/vnd.api+json) 1308 | 1309 | { 1310 | errors: [{ 1311 | "status": "401", 1312 | "title": "Unauthorized", 1313 | "detail": "Authorization header missing" 1314 | }] 1315 | } 1316 | 1317 | + Response 401 (application/vnd.api+json) 1318 | 1319 | { 1320 | errors: [{ 1321 | "status": "401", 1322 | "title": "Unauthorized", 1323 | "detail": "Session invalid" 1324 | }] 1325 | } 1326 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "account-json-api", 3 | "description": "RESTful API-blueprint for all things user accounts", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "npm run -s summary", 7 | "semantic-release": "semantic-release", 8 | "summary": "scripts/summary.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/hoodiehq/account-json-api.git" 13 | }, 14 | "keywords": [ 15 | "apiary", 16 | "api", 17 | "blueprint" 18 | ], 19 | "author": "Gregor Martynus ", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/hoodiehq/account-json-api/issues" 23 | }, 24 | "homepage": "https://github.com/hoodiehq/account-json-api#readme", 25 | "devDependencies": { 26 | "lodash": "^4.16.4", 27 | "protagonist": "^1.2.0", 28 | "semantic-release": "^15.0.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /scripts/summary.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs') 4 | 5 | var _ = require('lodash') 6 | var protagonist = require('protagonist') 7 | 8 | var source = fs.readFileSync('./apiary.apib').toString() 9 | var tree = protagonist.parseSync(source) 10 | 11 | var pathNodes = _.flattenDeep(findNodes(has('attributes.href'), tree)).filter(Boolean) 12 | 13 | pathNodes.forEach(function (pathNode, i) { 14 | var path = pathNode.attributes.href 15 | var methodNodes = _.flattenDeep(findNodes(has('attributes.method'), pathNode.content)).filter(Boolean) 16 | _.uniq(methodNodes, 'attributes.method').forEach(function (methodNode, j) { 17 | var method = methodNode.attributes.method 18 | console.log('%s %s', _.padEnd(method, 6), path) 19 | }) 20 | }) 21 | 22 | function has (property) { 23 | return function (object) { 24 | return _.has(object, property) 25 | } 26 | } 27 | 28 | function findNodes (check, content) { 29 | if (check(content)) { 30 | return content 31 | } 32 | 33 | if (Array.isArray(content)) { 34 | return content.map(findNodes.bind(null, check)) 35 | } 36 | 37 | if (content.content) { 38 | return findNodes(check, content.content) 39 | } 40 | } 41 | --------------------------------------------------------------------------------