diff --git a/lib/api3/doc/security.md b/lib/api3/doc/security.md index 49a2505ca0e..99972fe8830 100644 --- a/lib/api3/doc/security.md +++ b/lib/api3/doc/security.md @@ -20,8 +20,7 @@ The identity of the client is represented by the *subject* to whom the access le For each security *subject*, the system automatically generates an *access token* that is difficult to guess since it is derived from the secret *API_SECRET*. The *access token* must be included in every secured API operation to decode the client's identity and determine its authorization level. In this way, it is then possible to resolve whether the client has the permission required by a particular API operation. -There are two ways to authorize API calls: -- use `token` query parameter to pass the *access token*, eg. `token=testreadab-76eaff2418bfb7e0` +There is only one way to authorize API calls: - use so-called [JSON Web Tokens](https://jwt.io "JSON Web Tokens") - at first let the `/api/v2/authorization/request` generates you a particular JWT, eg. `GET https://nsapiv3.herokuapp.com/api/v2/authorization/request/testreadab-76eaff2418bfb7e0` - then, to each secure API operation attach a JWT token in the HTTP header, eg. `Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NUb2tlbiI6InRlc3RyZWFkYWItNzZlYWZmMjQxOGJmYjdlMCIsImlhdCI6MTU2NTAzOTczMSwiZXhwIjoxNTY1MDQzMzMxfQ.Y-OFtFJ-gZNJcnZfm9r4S7085Z7YKVPiaQxuMMnraVk` (until the JWT expires) diff --git a/lib/api3/doc/tutorial.md b/lib/api3/doc/tutorial.md index 50ab57c5b93..7ea482f6cff 100644 --- a/lib/api3/doc/tutorial.md +++ b/lib/api3/doc/tutorial.md @@ -16,22 +16,23 @@ It is public (there is no need to add authorization parameters/headers). Sample GET `/version` client code (to get actual versions): ```javascript -const request = require('request'); - -request('https://nsapiv3.herokuapp.com/api/v3/version', - (error, response, body) => console.log(body)); +const axios = require('axios'); +axios.get(`https://nsapiv3.herokuapp.com/api/v3/version`) + .then(res => { + console.log(res.data); + }); ``` Sample result: ```json { "status": 200, "result": { - "version": "14.1.0", - "apiVersion": "3.0.2-alpha", - "srvDate": 1609402081548, + "version": "14.2.0", + "apiVersion": "3.0.4-alpha", + "srvDate": 1613056980085, "storage": { "storage": "mongodb", - "version": "4.2.11" + "version": "4.4.3" } } } @@ -46,23 +47,33 @@ It is public (there is no need to add authorization parameters/headers). Sample GET `/status` client code (to get my actual permissions): ```javascript -const request = require('request'); -const auth = `token=testadmin-ad3b1f9d7b3f59d5`; - -request(`https://nsapiv3.herokuapp.com/api/v3/status?${auth}`, - (error, response, body) => console.log(body)); +const axios = require('axios'); +const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5'; +axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`) + .then(res => { + const jwt = res.data.token; + return axios.get(`https://nsapiv3.herokuapp.com/api/v3/status`, + { + headers: { + 'Authorization': `Bearer ${jwt}` + } + }); + }) + .then(res => { + console.log(res.data); + }); ``` Sample result: ```json { "status": 200, "result": { - "version": "14.1.0", - "apiVersion": "3.0.2-alpha", - "srvDate": 1609427571833, + "version": "14.2.0", + "apiVersion": "3.0.4-alpha", + "srvDate": 1613057148579, "storage": { "storage": "mongodb", - "version": "4.2.11" + "version": "4.4.3" }, "apiPermissions": { "devicestatus": "crud", @@ -85,31 +96,41 @@ Sample result: Sample GET `/entries` client code (to retrieve last 3 BG values): ```javascript -const request = require('request'); -const auth = `token=testadmin-ad3b1f9d7b3f59d5`; - -request(`https://nsapiv3.herokuapp.com/api/v3/entries?${auth}&sort$desc=date&limit=3&fields=dateString,sgv,direction`, - (error, response, body) => console.log(body)); +const axios = require('axios'); +const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5'; +axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`) + .then(res => { + const jwt = res.data.token; + return axios.get(`https://nsapiv3.herokuapp.com/api/v3/entries?sort$desc=date&limit=3&fields=dateString,sgv,direction`, + { + headers: { + 'Authorization': `Bearer ${jwt}` + } + }); + }) + .then(res => { + console.log(res.data); + }); ``` Sample result: ```json { - "status": 200, - "result": [ - { - "dateString": "2019-07-30T02:24:50.434+0200", - "sgv": 115, + "status": 200, + "result": [ + { + "dateString": "2021-02-11T15:25:28.928Z", + "sgv": 116, "direction": "FortyFiveDown" }, - { - "dateString": "2019-07-30T02:19:50.374+0200", - "sgv": 121, + { + "dateString": "2021-02-11T15:20:28.239Z", + "sgv": 124, "direction": "FortyFiveDown" }, - { - "dateString": "2019-07-30T02:14:50.450+0200", - "sgv": 129, - "direction": "FortyFiveDown" + { + "dateString": "2021-02-11T15:15:28.225Z", + "sgv": 130, + "direction": "Flat" } ] } @@ -123,29 +144,37 @@ Sample result: Sample POST `/treatments` client code: ```javascript -const request = require('request'); -const auth = `token=testadmin-ad3b1f9d7b3f59d5`; +const axios = require('axios'); +const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5'; const doc = { - date: 1564591511232, // (new Date()).getTime(), + date: 1613057404186, // (new Date()).getTime(), app: 'AndroidAPS', device: 'Samsung XCover 4-861536030196001', eventType: 'Correction Bolus', insulin: 0.3 }; -request({ - method: 'post', - body: doc, - json: true, - url: `https://nsapiv3.herokuapp.com/api/v3/treatments?${auth}` - }, - (error, response, body) => console.log(body)); +axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`) + .then(res => { + const jwt = res.data.token; + return axios(`https://nsapiv3.herokuapp.com/api/v3/treatments`, + { + method: 'post', + data: doc, + headers: { + 'Authorization': `Bearer ${jwt}` + } + }); + }) + .then(res => { + console.log(res.data); + }); ``` Sample result: ```json { "status": 201, - "identifier": "95e1a6e3-1146-5d6a-a3f1-41567cae0895", - "lastModified": 1564591511711 + "identifier": "5b0f7124-475f-5db0-824c-a73c5eea0975", + "lastModified": 1613057523148 } ``` @@ -157,28 +186,38 @@ Sample result: Sample GET `/treatments/{identifier}` client code: ```javascript -const request = require('request'); -const auth = `token=testadmin-ad3b1f9d7b3f59d5`; -const identifier = '95e1a6e3-1146-5d6a-a3f1-41567cae0895'; - -request(`https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}?${auth}`, - (error, response, body) => console.log(body)); +const axios = require('axios'); +const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5'; +const identifier = '5b0f7124-475f-5db0-824c-a73c5eea0975'; +axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`) + .then(res => { + const jwt = res.data.token; + return axios.get(`https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}`, + { + headers: { + 'Authorization': `Bearer ${jwt}` + } + }); + }) + .then(res => { + console.log(res.data); + }); ``` Sample result: ```json { "status": 200, - "result": { - "date": 1564591511232, + "result": { + "date": 1613057404186, "app": "AndroidAPS", "device": "Samsung XCover 4-861536030196001", "eventType": "Correction Bolus", "insulin": 0.3, - "identifier": "95e1a6e3-1146-5d6a-a3f1-41567cae0895", "utcOffset": 0, - "created_at": "2019-07-31T16:45:11.232Z", - "srvModified": 1564591627732, - "srvCreated": 1564591511711, + "created_at": "2021-02-11T15:30:04.186Z", + "identifier": "5b0f7124-475f-5db0-824c-a73c5eea0975", + "srvModified": 1613057523148, + "srvCreated": 1613057523148, "subject": "test-admin" } } @@ -192,23 +231,33 @@ Sample result: Sample GET `/lastModified` client code (to get latest modification dates): ```javascript -const request = require('request'); -const auth = `token=testadmin-ad3b1f9d7b3f59d5`; - -request(`https://nsapiv3.herokuapp.com/api/v3/lastModified?${auth}`, - (error, response, body) => console.log(body)); +const axios = require('axios'); +const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5'; +axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`) + .then(res => { + const jwt = res.data.token; + return axios.get(`https://nsapiv3.herokuapp.com/api/v3/lastModified`, + { + headers: { + 'Authorization': `Bearer ${jwt}` + } + }); + }) + .then(res => { + console.log(res.data); + }); ``` Sample result: ```json -{ +{ "status": 200, - "result": { - "srvDate": 1564591783202, - "collections": { - "devicestatus": 1564591490074, - "entries": 1564591486801, - "profile": 1548524042744, - "treatments": 1564591627732 + "result": { + "srvDate": 1613057924021, + "collections": { + "devicestatus": 1613057731281, + "entries": 1613057728148, + "profile": 1580337948416, + "treatments": 1613057523148 } } } @@ -222,29 +271,37 @@ Sample result: Sample PUT `/treatments/{identifier}` client code (to update `insulin` from 0.3 to 0.4): ```javascript -const request = require('request'); -const auth = `token=testadmin-ad3b1f9d7b3f59d5`; -const identifier = '95e1a6e3-1146-5d6a-a3f1-41567cae0895'; +const axios = require('axios'); +const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5'; +const identifier = '5b0f7124-475f-5db0-824c-a73c5eea0975'; const doc = { - date: 1564591511232, + date: 1613057404186, app: 'AndroidAPS', device: 'Samsung XCover 4-861536030196001', eventType: 'Correction Bolus', insulin: 0.4 }; - -request({ - method: 'put', - body: doc, - json: true, - url: `https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}?${auth}` - }, - (error, response, body) => console.log(body)); +axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`) + .then(res => { + const jwt = res.data.token; + return axios(`https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}`, + { + method: 'put', + data: doc, + headers: { + 'Authorization': `Bearer ${jwt}` + } + }); + }) + .then(res => { + console.log(res.data); + }); ``` Sample result: ```json { - "status": 200 + "status": 200, + "lastModified": 1613058295307 } ``` @@ -256,20 +313,27 @@ Sample result: Sample PATCH `/treatments/{identifier}` client code (to update `insulin` from 0.4 to 0.5): ```javascript -const request = require('request'); -const auth = `token=testadmin-ad3b1f9d7b3f59d5`; -const identifier = '95e1a6e3-1146-5d6a-a3f1-41567cae0895'; +const axios = require('axios'); +const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5'; +const identifier = '5b0f7124-475f-5db0-824c-a73c5eea0975'; const doc = { insulin: 0.5 }; - -request({ - method: 'patch', - body: doc, - json: true, - url: `https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}?${auth}` - }, - (error, response, body) => console.log(body)); +axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`) + .then(res => { + const jwt = res.data.token; + return axios(`https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}`, + { + method: 'patch', + data: doc, + headers: { + 'Authorization': `Bearer ${jwt}` + } + }); + }) + .then(res => { + console.log(res.data); + }); ``` Sample result: ```json @@ -284,17 +348,25 @@ Sample result: [DELETE](https://nsapiv3insecure.herokuapp.com/api3-docs/#/generic/delete__collection___identifier_) operation deletes existing document from the collection. -Sample DELETE `/treatments/{identifier}` client code (to update `insulin` from 0.4 to 0.5): +Sample DELETE `/treatments/{identifier}` client code: ```javascript -const request = require('request'); -const auth = `token=testadmin-ad3b1f9d7b3f59d5`; -const identifier = '95e1a6e3-1146-5d6a-a3f1-41567cae0895'; - -request({ - method: 'delete', - url: `https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}?${auth}` - }, - (error, response, body) => console.log(body)); +const axios = require('axios'); +const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5'; +const identifier = '5b0f7124-475f-5db0-824c-a73c5eea0975'; +axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`) + .then(res => { + const jwt = res.data.token; + return axios(`https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}`, + { + method: 'delete', + headers: { + 'Authorization': `Bearer ${jwt}` + } + }); + }) + .then(res => { + console.log(res.data); + }); ``` Sample result: ```json @@ -311,12 +383,22 @@ Sample result: Sample HISTORY `/treatments/history/{lastModified}` client code: ```javascript -const request = require('request'); -const auth = `token=testadmin-ad3b1f9d7b3f59d5`; -const lastModified = 1564521267421; - -request(`https://nsapiv3.herokuapp.com/api/v3/treatments/history/${lastModified}?${auth}`, - (error, response, body) => console.log(response.body)); +const axios = require('axios'); +const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5'; +const lastModified = 1613057520148; +axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`) + .then(res => { + const jwt = res.data.token; + return axios(`https://nsapiv3.herokuapp.com/api/v3/treatments/history/${lastModified}`, + { + headers: { + 'Authorization': `Bearer ${jwt}` + } + }); + }) + .then(res => { + console.log(res.data); + }); ``` Sample result: ```json @@ -324,32 +406,19 @@ Sample result: "status": 200, "result": [ { - "date": 1564521267421, + "date": 1613057404186, "app": "AndroidAPS", "device": "Samsung XCover 4-861536030196001", "eventType": "Correction Bolus", "insulin": 0.5, "utcOffset": 0, - "created_at": "2019-07-30T21:14:27.421Z", - "identifier": "95e1a6e3-1146-5d6a-a3f1-41567cae0895", - "srvModified": 1564592440416, - "srvCreated": 1564592334853, + "created_at": "2021-02-11T15:30:04.186Z", + "identifier": "5b0f7124-475f-5db0-824c-a73c5eea0975", + "srvModified": 1613058548149, + "srvCreated": 1613057523148, "subject": "test-admin", "modifiedBy": "test-admin", "isValid": false - }, - { - "date": 1564592545299, - "app": "AndroidAPS", - "device": "Samsung XCover 4-861536030196001", - "eventType": "Snack Bolus", - "carbs": 10, - "identifier": "267c43c2-f629-5191-a542-4f410c69e486", - "utcOffset": 0, - "created_at": "2019-07-31T17:02:25.299Z", - "srvModified": 1564592545781, - "srvCreated": 1564592545781, - "subject": "test-admin" } ] } diff --git a/lib/api3/security.js b/lib/api3/security.js index 57f57107724..c42e5a114bd 100644 --- a/lib/api3/security.js +++ b/lib/api3/security.js @@ -25,7 +25,14 @@ function authenticate (opCtx) { return resolve({ shiros: [ adminShiro ] }); } - let token = ctx.authorization.extractToken(req); + let token + if (req.header('Authorization')) { + const parts = req.header('Authorization').split(' '); + if (parts.length === 2 && parts[0].toLowerCase() === 'bearer') { + token = parts[1]; + } + } + if (!token) { return reject( opTools.sendJSONStatus(res, apiConst.HTTP.UNAUTHORIZED, apiConst.MSG.HTTP_401_MISSING_OR_BAD_TOKEN)); diff --git a/lib/api3/swagger.json b/lib/api3/swagger.json index 67a54d97d95..20a8830a214 100644 --- a/lib/api3/swagger.json +++ b/lib/api3/swagger.json @@ -11,7 +11,7 @@ "name": "AGPL 3", "url": "https://www.gnu.org/licenses/agpl.txt" }, - "version": "3.0.3" + "version": "3.0.4" }, "servers": [ { @@ -49,17 +49,6 @@ "$ref": "#/components/schemas/paramCollection" } }, - { - "name": "token", - "in": "query", - "description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample:\n\n
token=testadmin-bf2591231bd2c042", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "string" - } - }, { "name": "filter_parameters", "in": "query", @@ -175,7 +164,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -216,9 +205,6 @@ } }, "security": [ - { - "accessToken": [] - }, { "jwtoken": [] } @@ -241,17 +227,6 @@ "schema": { "$ref": "#/components/schemas/paramCollection" } - }, - { - "name": "token", - "in": "query", - "description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample:\n\n
token=testadmin-bf2591231bd2c042", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "string" - } } ], "requestBody": { @@ -313,7 +288,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -354,9 +329,6 @@ } }, "security": [ - { - "accessToken": [] - }, { "jwtoken": [] } @@ -393,17 +365,6 @@ "$ref": "#/components/schemas/paramIdentifier" } }, - { - "name": "token", - "in": "query", - "description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample:\n\n
token=testadmin-bf2591231bd2c042", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "string" - } - }, { "name": "If-Modified-Since", "in": "header", @@ -473,7 +434,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -524,9 +485,6 @@ } }, "security": [ - { - "accessToken": [] - }, { "jwtoken": [] } @@ -561,17 +519,6 @@ "$ref": "#/components/schemas/paramIdentifier" } }, - { - "name": "token", - "in": "query", - "description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample:\n\n
token=testadmin-bf2591231bd2c042", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "string" - } - }, { "name": "If-Unmodified-Since", "in": "header", @@ -632,7 +579,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -693,9 +640,6 @@ } }, "security": [ - { - "accessToken": [] - }, { "jwtoken": [] } @@ -730,17 +674,6 @@ "$ref": "#/components/schemas/paramIdentifier" } }, - { - "name": "token", - "in": "query", - "description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample:\n\n
token=testadmin-bf2591231bd2c042", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "string" - } - }, { "name": "permanent", "in": "query", @@ -765,7 +698,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -806,9 +739,6 @@ } }, "security": [ - { - "accessToken": [] - }, { "jwtoken": [] } @@ -843,17 +773,6 @@ "$ref": "#/components/schemas/paramIdentifier" } }, - { - "name": "token", - "in": "query", - "description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample:\n\n
token=testadmin-bf2591231bd2c042", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "string" - } - }, { "name": "If-Unmodified-Since", "in": "header", @@ -899,7 +818,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -960,9 +879,6 @@ } }, "security": [ - { - "accessToken": [] - }, { "jwtoken": [] } @@ -989,17 +905,6 @@ "$ref": "#/components/schemas/paramCollection" } }, - { - "name": "token", - "in": "query", - "description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample:\n\n
token=testadmin-bf2591231bd2c042", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "string" - } - }, { "name": "Last-Modified", "in": "header", @@ -1087,7 +992,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -1128,9 +1033,6 @@ } }, "security": [ - { - "accessToken": [] - }, { "jwtoken": [] } @@ -1169,17 +1071,6 @@ "format": "int64" } }, - { - "name": "token", - "in": "query", - "description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample:\n\n
token=testadmin-bf2591231bd2c042", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "string" - } - }, { "name": "limit", "in": "query", @@ -1256,7 +1147,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -1297,9 +1188,6 @@ } }, "security": [ - { - "accessToken": [] - }, { "jwtoken": [] } @@ -1346,7 +1234,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -1367,9 +1255,6 @@ } }, "security": [ - { - "accessToken": [] - }, { "jwtoken": [] } @@ -1384,19 +1269,6 @@ "summary": "LAST MODIFIED: Retrieves timestamp of the last modification of every collection", "description": "LAST MODIFIED operation inspects collections separately (in parallel) and for each of them it finds the date of any last modification (insertion, update, deletion).\nNot only `srvModified`, but also `date` and `created_at` fields are inspected (as a fallback to previous API).\n\nThis operation requires `read` permission for the API and the collections (e.g. `api:treatments:read`). For each collection the permission is checked separately, you will get timestamps only for those collections that you have access to.", "operationId": "LAST-MODIFIED", - "parameters": [ - { - "name": "token", - "in": "query", - "description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample:\n\n
token=testadmin-bf2591231bd2c042", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "string" - } - } - ], "responses": { "200": { "description": "Successful operation returning the timestamps", @@ -1409,7 +1281,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -1430,9 +1302,6 @@ } }, "security": [ - { - "accessToken": [] - }, { "jwtoken": [] } @@ -1545,7 +1414,7 @@ }, "subject": { "type": "string", - "description": "Name of the security subject (within Nightscout scope) which has created the document. This field is automatically set by the server from the passed token or JWT.\n\nNote: this field is immutable by the client (it cannot be updated or patched)", + "description": "Name of the security subject (within Nightscout scope) which has created the document. This field is automatically set by the server from the passed JWT.\n\nNote: this field is immutable by the client (it cannot be updated or patched)", "example": "uploader" }, "srvModified": { @@ -2352,7 +2221,7 @@ } }, "401Unauthorized": { - "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -2506,17 +2375,6 @@ } }, "parameters": { - "tokenParam": { - "name": "token", - "in": "query", - "description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample:\n\n
token=testadmin-bf2591231bd2c042", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "string" - } - }, "limitParam": { "name": "limit", "in": "query", @@ -2645,12 +2503,6 @@ } }, "securitySchemes": { - "accessToken": { - "type": "apiKey", - "description": "Add token as query item in the URL or as HTTP header. You can manage access token in `/admin`.\nEach operation requires a specific permission that has to be granted (via security role) to the security subject, which was authenticated by `token` parameter/header or `JWT`. E.g. for creating new `devicestatus` document via API you need `api:devicestatus:create` permission.", - "name": "token", - "in": "query" - }, "jwtoken": { "type": "http", "description": "Use this if you know the temporary json webtoken.", diff --git a/lib/api3/swagger.yaml b/lib/api3/swagger.yaml index c9b764a7409..332b00e86bf 100644 --- a/lib/api3/swagger.yaml +++ b/lib/api3/swagger.yaml @@ -2,7 +2,7 @@ openapi: 3.0.0 servers: - url: '/api/v3' info: - version: 3.0.3 + version: 3.0.4 title: Nightscout API contact: name: NS development discussion channel @@ -75,8 +75,6 @@ paths: schema: $ref: '#/components/schemas/paramCollection' - - $ref: '#/components/parameters/tokenParam' - ###################################################################################### get: tags: @@ -113,7 +111,6 @@ paths: - $ref: '#/components/parameters/fieldsParam' security: - - accessToken: [] - jwtoken: [] responses: @@ -158,7 +155,6 @@ paths: $ref: '#/components/schemas/DocumentToPost' security: - - accessToken: [] - jwtoken: [] responses: @@ -196,8 +192,6 @@ paths: schema: $ref: '#/components/schemas/paramIdentifier' - - $ref: '#/components/parameters/tokenParam' - ###################################################################################### get: tags: @@ -221,7 +215,6 @@ paths: - $ref: '#/components/parameters/fieldsParam' security: - - accessToken: [] - jwtoken: [] responses: @@ -276,7 +269,6 @@ paths: $ref: '#/components/schemas/DocumentToPost' security: - - accessToken: [] - jwtoken: [] responses: @@ -341,7 +333,6 @@ paths: $ref: '#/components/schemas/DocumentToPost' security: - - accessToken: [] - jwtoken: [] responses: @@ -382,7 +373,6 @@ paths: - $ref: '#/components/parameters/permanentParam' security: - - accessToken: [] - jwtoken: [] responses: @@ -408,8 +398,6 @@ paths: schema: $ref: '#/components/schemas/paramCollection' - - $ref: '#/components/parameters/tokenParam' - get: tags: - generic @@ -439,7 +427,6 @@ paths: - $ref: '#/components/parameters/fieldsParam' security: - - accessToken: [] - jwtoken: [] responses: @@ -475,8 +462,6 @@ paths: type: integer format: int64 - - $ref: '#/components/parameters/tokenParam' - get: tags: - generic @@ -497,7 +482,6 @@ paths: - $ref: '#/components/parameters/fieldsParam' security: - - accessToken: [] - jwtoken: [] responses: @@ -543,7 +527,6 @@ paths: This operation requires authorization in contrast with VERSION operation. security: - - accessToken: [] - jwtoken: [] responses: @@ -560,9 +543,6 @@ paths: ###################################################################################### /lastModified: - parameters: - - $ref: '#/components/parameters/tokenParam' - get: tags: - other @@ -577,7 +557,6 @@ paths: This operation requires `read` permission for the API and the collections (e.g. `api:treatments:read`). For each collection the permission is checked separately, you will get timestamps only for those collections that you have access to. security: - - accessToken: [] - jwtoken: [] responses: @@ -594,22 +573,6 @@ components: parameters: - tokenParam: - in: query - name: token - schema: - type: string - required: false - description: - An alternative way of authorization - passing accessToken in a query parameter. - - - Example: - - -
token=testadmin-bf2591231bd2c042- - limitParam: in: query name: limit @@ -887,7 +850,7 @@ components: example: 400 401Unauthorized: - description: The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy. + description: The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy. content: application/json: schema: @@ -1226,7 +1189,7 @@ components: subject: type: string description: - Name of the security subject (within Nightscout scope) which has created the document. This field is automatically set by the server from the passed token or JWT. + Name of the security subject (within Nightscout scope) which has created the document. This field is automatically set by the server from the passed JWT. Note: this field is immutable by the client (it cannot be updated or patched) @@ -1750,16 +1713,6 @@ components: ###################################################################################### securitySchemes: - accessToken: - type: apiKey - name: token - in: query - description: >- - Add token as query item in the URL or as HTTP header. You can manage access token in - `/admin`. - - Each operation requires a specific permission that has to be granted (via security role) to the security subject, which was authenticated by `token` parameter/header or `JWT`. E.g. for creating new `devicestatus` document via API you need `api:devicestatus:create` permission. - jwtoken: type: http scheme: bearer diff --git a/tests/api.security.test.js b/tests/api.security.test.js index 43e6f0ab074..fd76696f7bd 100644 --- a/tests/api.security.test.js +++ b/tests/api.security.test.js @@ -29,7 +29,7 @@ describe('Security of REST API V1', function() { self.app.use('/api/v2/authorization', ctx.authorization.endpoints); let authResult = await authSubject(ctx.authorization.storage); self.subject = authResult.subject; - self.token = authResult.token; + self.token = authResult.accessToken; done(); }); @@ -152,7 +152,7 @@ describe('Security of REST API V1', function() { .expect(200) .end(function(err, res) { res.body.message.message.should.equal('OK'); - res.body.message.isAdmin.should.equal(true); + res.body.message.isAdmin.should.equal(true); done(); }); }); diff --git a/tests/api3.create.test.js b/tests/api3.create.test.js index 5d155f4ecc1..054d3e3254b 100644 --- a/tests/api3.create.test.js +++ b/tests/api3.create.test.js @@ -29,7 +29,7 @@ describe('API3 CREATE', function() { * Cleanup after successful creation */ self.delete = async function deletePermanent (identifier) { - let res = await self.instance.delete(`${self.url}/${identifier}?permanent=true&token=${self.token.delete}`) + let res = await self.instance.delete(`${self.url}/${identifier}?permanent=true`, self.jwt.delete) .expect(200); res.body.status.should.equal(200); @@ -40,7 +40,7 @@ describe('API3 CREATE', function() { * Get document detail for futher processing */ self.get = async function get (identifier) { - let res = await self.instance.get(`${self.url}/${identifier}?token=${self.token.read}`) + let res = await self.instance.get(`${self.url}/${identifier}`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -52,7 +52,7 @@ describe('API3 CREATE', function() { * Get document detail for futher processing */ self.search = async function search (date) { - let res = await self.instance.get(`${self.url}?date$eq=${date}&token=${self.token.read}`) + let res = await self.instance.get(`${self.url}?date$eq=${date}`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -68,11 +68,16 @@ describe('API3 CREATE', function() { self.col = 'treatments' self.url = `/api/v3/${self.col}`; - let authResult = await authSubject(self.instance.ctx.authorization.storage); + let authResult = await authSubject(self.instance.ctx.authorization.storage, [ + 'create', + 'update', + 'read', + 'delete', + 'all' + ], self.instance.app); self.subject = authResult.subject; - self.token = authResult.token; - self.urlToken = `${self.url}?token=${self.token.create}`; + self.jwt = authResult.jwt; self.cache = self.instance.cacheMonitor; }); @@ -103,7 +108,7 @@ describe('API3 CREATE', function() { it('should not found not existing collection', async () => { - let res = await self.instance.post(`/api/v3/NOT_EXIST?token=${self.url}`) + let res = await self.instance.post(`/api/v3/NOT_EXIST`, self.jwt.create) .send(self.validDoc) .expect(404); @@ -113,7 +118,7 @@ describe('API3 CREATE', function() { it('should require create permission', async () => { - let res = await self.instance.post(`${self.url}?token=${self.token.read}`) + let res = await self.instance.post(`${self.url}`, self.jwt.read) .send(self.validDoc) .expect(403); @@ -123,7 +128,7 @@ describe('API3 CREATE', function() { it('should reject empty body', async () => { - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send({ }) .expect(400); @@ -132,7 +137,7 @@ describe('API3 CREATE', function() { it('should accept valid document', async () => { - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send(self.validDoc) .expect(201); @@ -161,7 +166,7 @@ describe('API3 CREATE', function() { let doc = Object.assign({}, self.validDoc); delete doc.date; - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send(doc) .expect(400); @@ -171,7 +176,7 @@ describe('API3 CREATE', function() { it('should reject invalid date null', async () => { - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send(Object.assign({}, self.validDoc, { date: null })) .expect(400); @@ -181,7 +186,7 @@ describe('API3 CREATE', function() { it('should reject invalid date ABC', async () => { - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send(Object.assign({}, self.validDoc, { date: 'ABC' })) .expect(400); @@ -191,7 +196,7 @@ describe('API3 CREATE', function() { it('should reject invalid date -1', async () => { - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send(Object.assign({}, self.validDoc, { date: -1 })) .expect(400); @@ -202,7 +207,7 @@ describe('API3 CREATE', function() { it('should reject invalid date 1 (too old)', async () => { - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send(Object.assign({}, self.validDoc, { date: 1 })) .expect(400); @@ -212,7 +217,7 @@ describe('API3 CREATE', function() { it('should reject invalid date - illegal format', async () => { - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send(Object.assign({}, self.validDoc, { date: '2019-20-60T50:90:90' })) .expect(400); @@ -222,7 +227,7 @@ describe('API3 CREATE', function() { it('should reject invalid utcOffset -5000', async () => { - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send(Object.assign({}, self.validDoc, { utcOffset: -5000 })) .expect(400); @@ -232,7 +237,7 @@ describe('API3 CREATE', function() { it('should reject invalid utcOffset ABC', async () => { - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send(Object.assign({}, self.validDoc, { utcOffset: 'ABC' })) .expect(400); @@ -244,7 +249,7 @@ describe('API3 CREATE', function() { it('should accept valid utcOffset', async () => { const doc = Object.assign({}, self.validDoc, { utcOffset: 120 }); - await self.instance.post(self.urlToken) + await self.instance.post(self.url, self.jwt.create) .send(doc) .expect(201); @@ -258,7 +263,7 @@ describe('API3 CREATE', function() { it('should reject invalid utcOffset null', async () => { - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send(Object.assign({}, self.validDoc, { utcOffset: null })) .expect(400); @@ -271,7 +276,7 @@ describe('API3 CREATE', function() { let doc = Object.assign({}, self.validDoc); delete doc.app; - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send(doc) .expect(400); @@ -281,7 +286,7 @@ describe('API3 CREATE', function() { it('should reject invalid app null', async () => { - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send(Object.assign({}, self.validDoc, { app: null })) .expect(400); @@ -291,7 +296,7 @@ describe('API3 CREATE', function() { it('should reject empty app', async () => { - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send(Object.assign({}, self.validDoc, { app: '' })) .expect(400); @@ -301,7 +306,7 @@ describe('API3 CREATE', function() { it('should normalize date and store utcOffset', async () => { - await self.instance.post(self.urlToken) + await self.instance.post(self.url, self.jwt.create) .send(Object.assign({}, self.validDoc, { date: '2019-06-10T08:07:08,576+02:00' })) .expect(201); @@ -321,7 +326,7 @@ describe('API3 CREATE', function() { const doc = Object.assign({}, self.validDoc); - await self.instance.post(self.urlToken) + await self.instance.post(self.url, self.jwt.create) .send(doc) .expect(201); @@ -330,7 +335,7 @@ describe('API3 CREATE', function() { self.cache.nextShouldEql(self.col, doc) const doc2 = Object.assign({}, doc); - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send(doc2) .expect(403); @@ -347,7 +352,7 @@ describe('API3 CREATE', function() { const doc = Object.assign({}, self.validDoc); - await self.instance.post(self.urlToken) + await self.instance.post(self.url, self.jwt.create) .send(doc) .expect(201); @@ -359,7 +364,7 @@ describe('API3 CREATE', function() { insulin: 0.5 }); - let resPost2 = await self.instance.post(`${self.url}?token=${self.token.all}`) + let resPost2 = await self.instance.post(`${self.url}`, self.jwt.all) .send(doc2) .expect(200); @@ -399,7 +404,7 @@ describe('API3 CREATE', function() { }); delete doc2._id; // APIv1 updates input document, we must get rid of _id for the next round - const resPost2 = await self.instance.post(`${self.url}?token=${self.token.all}`) + const resPost2 = await self.instance.post(`${self.url}`, self.jwt.all) .send(doc2) .expect(200); @@ -447,7 +452,7 @@ describe('API3 CREATE', function() { identifier: utils.randomString('32', 'aA#') }); - await self.instance.post(`${self.url}?token=${self.token.all}`) + await self.instance.post(`${self.url}`, self.jwt.all) .send(doc2) .expect(201); @@ -471,18 +476,18 @@ describe('API3 CREATE', function() { , identifier = utils.randomString('32', 'aA#') , doc = Object.assign({}, self.validDoc, { identifier, date: date1.toISOString() }); - await self.instance.post(self.urlToken) + await self.instance.post(self.url, self.jwt.create) .send(doc) .expect(201); self.cache.nextShouldEql(self.col, Object.assign({}, doc, { date: date1.getTime() })); - let res = await self.instance.delete(`${self.url}/${identifier}?token=${self.token.delete}`) + let res = await self.instance.delete(`${self.url}/${identifier}`, self.jwt.delete) .expect(200); res.body.status.should.equal(200); self.cache.nextShouldDeleteLast(self.col) const date2 = new Date(); - res = await self.instance.post(self.urlToken) + res = await self.instance.post(self.url, self.jwt.create) .send(Object.assign({}, self.validDoc, { identifier, date: date2.toISOString() })) .expect(403); @@ -491,7 +496,7 @@ describe('API3 CREATE', function() { self.cache.shouldBeEmpty() const doc2 = Object.assign({}, self.validDoc, { identifier, date: date2.toISOString() }); - res = await self.instance.post(`${self.url}?token=${self.token.all}`) + res = await self.instance.post(`${self.url}`, self.jwt.all) .send(doc2) .expect(200); @@ -513,7 +518,7 @@ describe('API3 CREATE', function() { delete self.validDoc.identifier; const validIdentifier = opTools.calculateIdentifier(self.validDoc); - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send(self.validDoc) .expect(201); @@ -536,7 +541,7 @@ describe('API3 CREATE', function() { delete self.validDoc.identifier; const validIdentifier = opTools.calculateIdentifier(self.validDoc); - let res = await self.instance.post(self.urlToken) + let res = await self.instance.post(self.url, self.jwt.create) .send(self.validDoc) .expect(201); @@ -550,7 +555,7 @@ describe('API3 CREATE', function() { self.cache.nextShouldEql(self.col, self.validDoc); delete self.validDoc.identifier; - res = await self.instance.post(`${self.url}?token=${self.token.update}`) + res = await self.instance.post(`${self.url}`, self.jwt.update) .send(self.validDoc) .expect(200); diff --git a/tests/api3.delete.test.js b/tests/api3.delete.test.js index fb15754ed9d..7d709fe5e6b 100644 --- a/tests/api3.delete.test.js +++ b/tests/api3.delete.test.js @@ -19,11 +19,12 @@ describe('API3 UPDATE', function() { self.env = self.instance.env; self.url = '/api/v3/treatments'; - let authResult = await authSubject(self.instance.ctx.authorization.storage); + let authResult = await authSubject(self.instance.ctx.authorization.storage, [ + 'delete' + ], self.instance.app); self.subject = authResult.subject; - self.token = authResult.token; - self.urlToken = `${self.url}?token=${self.token.delete}`; + self.jwt = authResult.jwt; self.cache = self.instance.cacheMonitor; }); @@ -53,7 +54,7 @@ describe('API3 UPDATE', function() { it('should not found not existing collection', async () => { - let res = await self.instance.delete(`/api/v3/NOT_EXIST?token=${self.url}`) + let res = await self.instance.delete(`/api/v3/NOT_EXIST`, self.jwt.delete) .send(self.validDoc) .expect(404); diff --git a/tests/api3.generic.workflow.test.js b/tests/api3.generic.workflow.test.js index a4888505fe8..01b5ede3394 100644 --- a/tests/api3.generic.workflow.test.js +++ b/tests/api3.generic.workflow.test.js @@ -36,11 +36,15 @@ describe('Generic REST API3', function() { self.urlResource = self.urlCol + '/' + self.identifier; self.urlHistory = self.urlCol + '/history'; - let authResult = await authSubject(self.instance.ctx.authorization.storage); + let authResult = await authSubject(self.instance.ctx.authorization.storage, [ + 'create', + 'update', + 'read', + 'delete' + ], self.instance.app); self.subject = authResult.subject; - self.token = authResult.token; - self.urlToken = `${self.url}?token=${self.token.create}`; + self.jwt = authResult.jwt; self.cache = self.instance.cacheMonitor; }); @@ -62,7 +66,7 @@ describe('Generic REST API3', function() { self.checkHistoryExistence = async function checkHistoryExistence (assertions) { - let res = await self.instance.get(`${self.urlHistory}/${self.historyTimestamp}?token=${self.token.read}`) + let res = await self.instance.get(`${self.urlHistory}/${self.historyTimestamp}`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -81,7 +85,7 @@ describe('Generic REST API3', function() { it('LAST MODIFIED to get actual server timestamp', async () => { - let res = await self.instance.get(`${self.urlLastModified}?token=${self.token.read}`) + let res = await self.instance.get(`${self.urlLastModified}`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -94,7 +98,7 @@ describe('Generic REST API3', function() { it('STATUS to get actual server timestamp', async () => { - let res = await self.instance.get(`/api/v3/status?token=${self.token.read}`) + let res = await self.instance.get(`/api/v3/status`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -104,13 +108,13 @@ describe('Generic REST API3', function() { it('READ of not existing document is not found', async () => { - await self.instance.get(`${self.urlResource}?token=${self.token.read}`) + await self.instance.get(`${self.urlResource}`, self.jwt.read) .expect(404); }); it('SEARCH of not existing document (not found)', async () => { - let res = await self.instance.get(`${self.urlCol}?token=${self.token.read}`) + let res = await self.instance.get(`${self.urlCol}`, self.jwt.read) .query({ 'identifier_eq': self.identifier }) .expect(200); @@ -120,13 +124,13 @@ describe('Generic REST API3', function() { it('DELETE of not existing document is not found', async () => { - await self.instance.delete(`${self.urlResource}?token=${self.token.delete}`) + await self.instance.delete(`${self.urlResource}`, self.jwt.delete) .expect(404); }); it('CREATE new document', async () => { - await self.instance.post(`${self.urlCol}?token=${self.token.create}`) + await self.instance.post(`${self.urlCol}`, self.jwt.create) .send(self.docOriginal) .expect(201); @@ -135,7 +139,7 @@ describe('Generic REST API3', function() { it('READ existing document', async () => { - let res = await self.instance.get(`${self.urlResource}?token=${self.token.read}`) + let res = await self.instance.get(`${self.urlResource}`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -149,7 +153,7 @@ describe('Generic REST API3', function() { it('SEARCH existing document (found)', async () => { - let res = await self.instance.get(`${self.urlCol}?token=${self.token.read}`) + let res = await self.instance.get(`${self.urlCol}`, self.jwt.read) .query({ 'identifier$eq': self.identifier }) .expect(200); @@ -169,7 +173,7 @@ describe('Generic REST API3', function() { it('UPDATE document', async () => { self.docActual.insulin = 0.5; - let res = await self.instance.put(`${self.urlResource}?token=${self.token.update}`) + let res = await self.instance.put(`${self.urlResource}`, self.jwt.update) .send(self.docActual) .expect(200); @@ -187,7 +191,7 @@ describe('Generic REST API3', function() { it('document changed in READ', async () => { - let res = await self.instance.get(`${self.urlResource}?token=${self.token.read}`) + let res = await self.instance.get(`${self.urlResource}`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -201,7 +205,7 @@ describe('Generic REST API3', function() { self.docActual.carbs = 5; self.docActual.insulin = 0.4; - let res = await self.instance.patch(`${self.urlResource}?token=${self.token.update}`) + let res = await self.instance.patch(`${self.urlResource}`, self.jwt.update) .send({ 'carbs': self.docActual.carbs, 'insulin': self.docActual.insulin }) .expect(200); @@ -218,7 +222,7 @@ describe('Generic REST API3', function() { it('document changed in READ', async () => { - let res = await self.instance.get(`${self.urlResource}?token=${self.token.read}`) + let res = await self.instance.get(`${self.urlResource}`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -229,7 +233,7 @@ describe('Generic REST API3', function() { it('soft DELETE', async () => { - let res = await self.instance.delete(`${self.urlResource}?token=${self.token.delete}`) + let res = await self.instance.delete(`${self.urlResource}`, self.jwt.delete) .expect(200); res.body.status.should.equal(200); @@ -238,14 +242,14 @@ describe('Generic REST API3', function() { it('READ of deleted is gone', async () => { - await self.instance.get(`${self.urlResource}?token=${self.token.read}`) + await self.instance.get(`${self.urlResource}`, self.jwt.read) .expect(410); }); it('SEARCH of deleted document missing it', async () => { - let res = await self.instance.get(`${self.urlCol}?token=${self.token.read}`) + let res = await self.instance.get(`${self.urlCol}`, self.jwt.read) .query({ 'identifier_eq': self.identifier }) .expect(200); @@ -262,7 +266,7 @@ describe('Generic REST API3', function() { it('permanent DELETE', async () => { - let res = await self.instance.delete(`${self.urlResource}?token=${self.token.delete}`) + let res = await self.instance.delete(`${self.urlResource}`, self.jwt.delete) .query({ 'permanent': 'true' }) .expect(200); @@ -272,13 +276,13 @@ describe('Generic REST API3', function() { it('READ of permanently deleted is not found', async () => { - await self.instance.get(`${self.urlResource}?token=${self.token.read}`) + await self.instance.get(`${self.urlResource}`, self.jwt.read) .expect(404); }); it('document permanently deleted not in HISTORY', async () => { - let res = await self.instance.get(`${self.urlHistory}/${self.historyTimestamp}?token=${self.token.read}`); + let res = await self.instance.get(`${self.urlHistory}/${self.historyTimestamp}`, self.jwt.read); res.body.status.should.equal(200); res.body.result.should.matchEach(value => { @@ -288,11 +292,11 @@ describe('Generic REST API3', function() { it('should not modify read-only document', async () => { - await self.instance.post(`${self.urlCol}?token=${self.token.create}`) + await self.instance.post(`${self.urlCol}`, self.jwt.create) .send(Object.assign({}, self.docOriginal, { isReadOnly: true })) .expect(201); - let res = await self.instance.get(`${self.urlResource}?token=${self.token.read}`) + let res = await self.instance.get(`${self.urlResource}`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -303,27 +307,27 @@ describe('Generic REST API3', function() { self.cache.nextShouldEql(self.col, self.docActual) self.cache.shouldBeEmpty() - res = await self.instance.post(`${self.urlCol}?token=${self.token.update}`) + res = await self.instance.post(`${self.urlCol}`, self.jwt.update) .send(Object.assign({}, self.docActual, { insulin: 0.41 })) .expect(422); res.body.message.should.equal(readOnlyMessage); - res = await self.instance.put(`${self.urlResource}?token=${self.token.update}`) + res = await self.instance.put(`${self.urlResource}`, self.jwt.update) .send(Object.assign({}, self.docActual, { insulin: 0.42 })) .expect(422); res.body.message.should.equal(readOnlyMessage); - res = await self.instance.patch(`${self.urlResource}?token=${self.token.update}`) + res = await self.instance.patch(`${self.urlResource}`, self.jwt.update) .send({ insulin: 0.43 }) .expect(422); res.body.message.should.equal(readOnlyMessage); - res = await self.instance.delete(`${self.urlResource}?token=${self.token.delete}`) + res = await self.instance.delete(`${self.urlResource}`, self.jwt.delete) .query({ 'permanent': 'true' }) .expect(422); res.body.message.should.equal(readOnlyMessage); - res = await self.instance.get(`${self.urlResource}?token=${self.token.read}`) + res = await self.instance.get(`${self.urlResource}`, self.jwt.read) .expect(200); res.body.status.should.equal(200); res.body.result.should.containEql(self.docOriginal); diff --git a/tests/api3.patch.test.js b/tests/api3.patch.test.js index 8bf13a68f4f..3e729d2dd56 100644 --- a/tests/api3.patch.test.js +++ b/tests/api3.patch.test.js @@ -28,7 +28,7 @@ describe('API3 PATCH', function() { * Get document detail for futher processing */ self.get = async function get (identifier) { - let res = await self.instance.get(`${self.url}/${identifier}?token=${self.token.read}`) + let res = await self.instance.get(`${self.url}/${identifier}`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -44,11 +44,15 @@ describe('API3 PATCH', function() { self.col = 'treatments'; self.url = `/api/v3/${self.col}`; - let authResult = await authSubject(self.instance.ctx.authorization.storage); + let authResult = await authSubject(self.instance.ctx.authorization.storage, [ + 'create', + 'update', + 'read' + ], self.instance.app); self.subject = authResult.subject; - self.token = authResult.token; - self.urlToken = `${self.url}/${self.validDoc.identifier}?token=${self.token.update}`; + self.jwt = authResult.jwt; + self.urlIdent = `${self.url}/${self.validDoc.identifier}`; self.cache = self.instance.cacheMonitor; }); @@ -78,7 +82,7 @@ describe('API3 PATCH', function() { it('should not found not existing collection', async () => { - let res = await self.instance.patch(`/api/v3/NOT_EXIST?token=${self.url}`) + let res = await self.instance.patch(`/api/v3/NOT_EXIST`, self.jwt.update) .send(self.validDoc) .expect(404); @@ -87,14 +91,14 @@ describe('API3 PATCH', function() { it('should not found not existing document', async () => { - let res = await self.instance.patch(self.urlToken) + let res = await self.instance.patch(self.urlIdent, self.jwt.update) .send(self.validDoc) .expect(404); res.body.status.should.equal(404); // now let's insert the document for further patching - res = await self.instance.post(`${self.url}?token=${self.token.create}`) + res = await self.instance.post(`${self.url}`, self.jwt.create) .send(self.validDoc) .expect(201); @@ -104,7 +108,7 @@ describe('API3 PATCH', function() { it('should reject identifier alteration', async () => { - let res = await self.instance.patch(self.urlToken) + let res = await self.instance.patch(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { identifier: 'MODIFIED'})) .expect(400); @@ -114,7 +118,7 @@ describe('API3 PATCH', function() { it('should reject date alteration', async () => { - let res = await self.instance.patch(self.urlToken) + let res = await self.instance.patch(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { date: self.validDoc.date + 10000 })) .expect(400); @@ -124,7 +128,7 @@ describe('API3 PATCH', function() { it('should reject utcOffset alteration', async () => { - let res = await self.instance.patch(self.urlToken) + let res = await self.instance.patch(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { utcOffset: self.utcOffset - 120 })) .expect(400); @@ -134,7 +138,7 @@ describe('API3 PATCH', function() { it('should reject eventType alteration', async () => { - let res = await self.instance.patch(self.urlToken) + let res = await self.instance.patch(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { eventType: 'MODIFIED' })) .expect(400); @@ -144,7 +148,7 @@ describe('API3 PATCH', function() { it('should reject device alteration', async () => { - let res = await self.instance.patch(self.urlToken) + let res = await self.instance.patch(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { device: 'MODIFIED' })) .expect(400); @@ -154,7 +158,7 @@ describe('API3 PATCH', function() { it('should reject app alteration', async () => { - let res = await self.instance.patch(self.urlToken) + let res = await self.instance.patch(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { app: 'MODIFIED' })) .expect(400); @@ -164,7 +168,7 @@ describe('API3 PATCH', function() { it('should reject srvCreated alteration', async () => { - let res = await self.instance.patch(self.urlToken) + let res = await self.instance.patch(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { srvCreated: self.validDoc.date - 10000 })) .expect(400); @@ -174,7 +178,7 @@ describe('API3 PATCH', function() { it('should reject subject alteration', async () => { - let res = await self.instance.patch(self.urlToken) + let res = await self.instance.patch(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { subject: 'MODIFIED' })) .expect(400); @@ -184,7 +188,7 @@ describe('API3 PATCH', function() { it('should reject srvModified alteration', async () => { - let res = await self.instance.patch(self.urlToken) + let res = await self.instance.patch(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { srvModified: self.validDoc.date - 100000 })) .expect(400); @@ -194,7 +198,7 @@ describe('API3 PATCH', function() { it('should reject modifiedBy alteration', async () => { - let res = await self.instance.patch(self.urlToken) + let res = await self.instance.patch(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { modifiedBy: 'MODIFIED' })) .expect(400); @@ -204,7 +208,7 @@ describe('API3 PATCH', function() { it('should reject isValid alteration', async () => { - let res = await self.instance.patch(self.urlToken) + let res = await self.instance.patch(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { isValid: false })) .expect(400); @@ -216,7 +220,7 @@ describe('API3 PATCH', function() { it('should patch document', async () => { self.validDoc.carbs = 10; - let res = await self.instance.patch(self.urlToken) + let res = await self.instance.patch(self.urlIdent, self.jwt.update) .send(self.validDoc) .expect(200); diff --git a/tests/api3.read.test.js b/tests/api3.read.test.js index 7a330769f7c..c0dbbf1fe32 100644 --- a/tests/api3.read.test.js +++ b/tests/api3.read.test.js @@ -31,10 +31,14 @@ describe('API3 READ', function () { self.col = 'devicestatus'; self.url = `/api/v3/${self.col}`; - let authResult = await authSubject(self.instance.ctx.authorization.storage); + let authResult = await authSubject(self.instance.ctx.authorization.storage, [ + 'create', + 'read', + 'delete' + ], self.instance.app); self.subject = authResult.subject; - self.token = authResult.token; + self.jwt = authResult.jwt; self.cache = self.instance.cacheMonitor; }); @@ -64,7 +68,7 @@ describe('API3 READ', function () { it('should not found not existing collection', async () => { - let res = await self.instance.get(`/api/v3/NOT_EXIST/NOT_EXIST?token=${self.url}`) + let res = await self.instance.get(`/api/v3/NOT_EXIST/NOT_EXIST`, self.jwt.read) .send(self.validDoc) .expect(404); @@ -75,7 +79,7 @@ describe('API3 READ', function () { it('should not found not existing document', async () => { - let res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?token=${self.token.read}`) + let res = await self.instance.get(`${self.url}/${self.validDoc.identifier}`, self.jwt.read) .expect(404); res.body.status.should.equal(404); @@ -85,13 +89,13 @@ describe('API3 READ', function () { it('should read just created document', async () => { - let res = await self.instance.post(`${self.url}?token=${self.token.create}`) + let res = await self.instance.post(`${self.url}`, self.jwt.create) .send(self.validDoc) .expect(201); res.body.status.should.equal(201); - res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?token=${self.token.read}`) + res = await self.instance.get(`${self.url}/${self.validDoc.identifier}`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -107,7 +111,7 @@ describe('API3 READ', function () { it('should contain only selected fields', async () => { - let res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?fields=date,device,subject&token=${self.token.read}`) + let res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?fields=date,device,subject`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -121,7 +125,7 @@ describe('API3 READ', function () { it('should contain all fields', async () => { - let res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?fields=_all&token=${self.token.read}`) + let res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?fields=_all`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -132,7 +136,7 @@ describe('API3 READ', function () { it('should not send unmodified document since', async () => { - let res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?token=${self.token.read}`) + let res = await self.instance.get(`${self.url}/${self.validDoc.identifier}`, self.jwt.read) .set('If-Modified-Since', new Date(new Date().getTime() + 1000).toUTCString()) .expect(304); @@ -141,7 +145,7 @@ describe('API3 READ', function () { it('should send modified document since', async () => { - let res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?token=${self.token.read}`) + let res = await self.instance.get(`${self.url}/${self.validDoc.identifier}`, self.jwt.read) .set('If-Modified-Since', new Date(new Date(self.validDoc.date).getTime() - 1000).toUTCString()) .expect(200); @@ -151,13 +155,13 @@ describe('API3 READ', function () { it('should recognize softly deleted document', async () => { - let res = await self.instance.delete(`${self.url}/${self.validDoc.identifier}?token=${self.token.delete}`) + let res = await self.instance.delete(`${self.url}/${self.validDoc.identifier}`, self.jwt.delete) .expect(200); res.body.status.should.equal(200); self.cache.nextShouldDeleteLast(self.col) - res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?token=${self.token.read}`) + res = await self.instance.get(`${self.url}/${self.validDoc.identifier}`, self.jwt.read) .expect(410); res.body.status.should.equal(410); @@ -166,13 +170,13 @@ describe('API3 READ', function () { it('should not find permanently deleted document', async () => { - let res = await self.instance.delete(`${self.url}/${self.validDoc.identifier}?permanent=true&token=${self.token.delete}`) + let res = await self.instance.delete(`${self.url}/${self.validDoc.identifier}?permanent=true`, self.jwt.delete) .expect(200); res.body.status.should.equal(200); self.cache.nextShouldDeleteLast(self.col) - res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?token=${self.token.read}`) + res = await self.instance.get(`${self.url}/${self.validDoc.identifier}`, self.jwt.read) .expect(404); res.body.status.should.equal(404); @@ -201,13 +205,13 @@ describe('API3 READ', function () { const identifier = doc._id.toString(); delete doc._id; - let res = await self.instance.get(`${self.url}/${identifier}?token=${self.token.read}`) + let res = await self.instance.get(`${self.url}/${identifier}`, self.jwt.read) .expect(200); res.body.status.should.equal(200); res.body.result.should.containEql(doc); - res = await self.instance.delete(`${self.url}/${identifier}?permanent=true&token=${self.token.delete}`) + res = await self.instance.delete(`${self.url}/${identifier}?permanent=true`, self.jwt.delete) .expect(200); res.body.status.should.equal(200); diff --git a/tests/api3.renderer.test.js b/tests/api3.renderer.test.js index d897cf96306..5946a431923 100644 --- a/tests/api3.renderer.test.js +++ b/tests/api3.renderer.test.js @@ -44,10 +44,14 @@ describe('API3 output renderers', function() { self.col = 'entries'; self.url = `/api/v3/${self.col}`; - let authResult = await authSubject(self.instance.ctx.authorization.storage); + let authResult = await authSubject(self.instance.ctx.authorization.storage, [ + 'create', + 'read', + 'delete' + ], self.instance.app); self.subject = authResult.subject; - self.token = authResult.token; + self.jwt = authResult.jwt; self.cache = self.instance.cacheMonitor; }); @@ -139,13 +143,13 @@ describe('API3 output renderers', function() { async function createDoc (doc) { - let res = await self.instance.post(`${self.url}?token=${self.token.create}`) + let res = await self.instance.post(`${self.url}`, self.jwt.create) .send(doc) .expect(201); res.body.status.should.equal(201); - res = await self.instance.get(`${self.url}/${doc.identifier}?token=${self.token.read}`) + res = await self.instance.get(`${self.url}/${doc.identifier}`, self.jwt.read) .expect(200); return res.body; } @@ -168,22 +172,22 @@ describe('API3 output renderers', function() { should.not.exist(res.body.result); } - await check406(self.instance.get(`${self.url}/${self.doc1.identifier}.ttf?fields=_all&token=${self.token.read}`)); - await check406(self.instance.get(`${self.url}/${self.doc1.identifier}?fields=_all&token=${self.token.read}`) + await check406(self.instance.get(`${self.url}/${self.doc1.identifier}.ttf?fields=_all`, self.jwt.read)); + await check406(self.instance.get(`${self.url}/${self.doc1.identifier}?fields=_all`, self.jwt.read) .set('Accept', 'font/ttf')); - await check406(self.instance.get(`${self.url}.ttf?fields=_all&token=${self.token.read}`)); - await check406(self.instance.get(`${self.url}?fields=_all&token=${self.token.read}`) + await check406(self.instance.get(`${self.url}.ttf?fields=_all`, self.jwt.read)); + await check406(self.instance.get(`${self.url}?fields=_all`, self.jwt.read) .set('Accept', 'font/ttf')); - await check406(self.instance.get(`${self.url}/history/${self.doc1.date}.ttf?token=${self.token.read}`)); - await check406(self.instance.get(`${self.url}/history/${self.doc1.date}?token=${self.token.read}`) + await check406(self.instance.get(`${self.url}/history/${self.doc1.date}.ttf`, self.jwt.read)); + await check406(self.instance.get(`${self.url}/history/${self.doc1.date}`, self.jwt.read) .set('Accept', 'font/ttf')); }); it('READ should accept xml content type', async () => { - let res = await self.instance.get(`${self.url}/${self.doc1.identifier}.xml?fields=_all&token=${self.token.read}`) + let res = await self.instance.get(`${self.url}/${self.doc1.identifier}.xml?fields=_all`, self.jwt.read) .expect(200); res.text.should.startWith(''); @@ -192,7 +196,7 @@ describe('API3 output renderers', function() { xml.item.should.not.be.empty(); self.checkProps(self.doc1, xml.item); - let res2 = await self.instance.get(`${self.url}/${self.doc1.identifier}?fields=_all&token=${self.token.read}`) + let res2 = await self.instance.get(`${self.url}/${self.doc1.identifier}?fields=_all`, self.jwt.read) .set('Accept', 'application/xml') .expect(200); @@ -201,12 +205,12 @@ describe('API3 output renderers', function() { it('READ should accept csv content type', async () => { - let res = await self.instance.get(`${self.url}/${self.doc1.identifier}.csv?fields=_all&token=${self.token.read}`) + let res = await self.instance.get(`${self.url}/${self.doc1.identifier}.csv?fields=_all`, self.jwt.read) .expect(200); await self.checkCsvItems([self.doc1], res.text); - let res2 = await self.instance.get(`${self.url}/${self.doc1.identifier}?fields=_all&token=${self.token.read}`) + let res2 = await self.instance.get(`${self.url}/${self.doc1.identifier}?fields=_all`, self.jwt.read) .set('Accept', 'text/csv') .expect(200); @@ -215,12 +219,12 @@ describe('API3 output renderers', function() { it('SEARCH should accept xml content type', async () => { - let res = await self.instance.get(`${self.url}.xml?token=${self.token.read}&date$gte=${self.doc1.date}`) + let res = await self.instance.get(`${self.url}.xml?date$gte=${self.doc1.date}`, self.jwt.read) .expect(200); await self.checkXmlItems([self.doc1, self.doc2], res.text); - let res2 = await self.instance.get(`${self.url}?token=${self.token.read}&date$gte=${self.doc1.date}`) + let res2 = await self.instance.get(`${self.url}?date$gte=${self.doc1.date}`, self.jwt.read) .set('Accept', 'application/xml') .expect(200); @@ -229,12 +233,12 @@ describe('API3 output renderers', function() { it('SEARCH should accept csv content type', async () => { - let res = await self.instance.get(`${self.url}.csv?token=${self.token.read}&date$gte=${self.doc1.date}`) + let res = await self.instance.get(`${self.url}.csv?date$gte=${self.doc1.date}`, self.jwt.read) .expect(200); await self.checkCsvItems([self.doc1, self.doc2], res.text); - let res2 = await self.instance.get(`${self.url}?token=${self.token.read}&date$gte=${self.doc1.date}`) + let res2 = await self.instance.get(`${self.url}?date$gte=${self.doc1.date}`, self.jwt.read) .set('Accept', 'text/csv') .expect(200); @@ -243,12 +247,12 @@ describe('API3 output renderers', function() { it('HISTORY should accept xml content type', async () => { - let res = await self.instance.get(`${self.url}/history/${self.historyFrom}.xml?token=${self.token.read}`) + let res = await self.instance.get(`${self.url}/history/${self.historyFrom}.xml`, self.jwt.read) .expect(200); await self.checkXmlItems([self.doc1, self.doc2], res.text); - let res2 = await self.instance.get(`${self.url}/history/${self.historyFrom}?token=${self.token.read}`) + let res2 = await self.instance.get(`${self.url}/history/${self.historyFrom}`, self.jwt.read) .set('Accept', 'application/xml') .expect(200); @@ -257,12 +261,12 @@ describe('API3 output renderers', function() { it('HISTORY should accept csv content type', async () => { - let res = await self.instance.get(`${self.url}/history/${self.historyFrom}.csv?token=${self.token.read}`) + let res = await self.instance.get(`${self.url}/history/${self.historyFrom}.csv`, self.jwt.read) .expect(200); await self.checkCsvItems([self.doc1, self.doc2], res.text); - let res2 = await self.instance.get(`${self.url}/history/${self.historyFrom}?token=${self.token.read}`) + let res2 = await self.instance.get(`${self.url}/history/${self.historyFrom}`, self.jwt.read) .set('Accept', 'text/csv') .expect(200); @@ -273,7 +277,7 @@ describe('API3 output renderers', function() { it('should remove mock documents', async () => { async function deleteDoc (identifier) { - let res = await self.instance.delete(`${self.url}/${identifier}?token=${self.token.delete}`) + let res = await self.instance.delete(`${self.url}/${identifier}`, self.jwt.delete) .query({ 'permanent': 'true' }) .expect(200); diff --git a/tests/api3.search.test.js b/tests/api3.search.test.js index d504bdb7937..aeafc84630c 100644 --- a/tests/api3.search.test.js +++ b/tests/api3.search.test.js @@ -21,7 +21,7 @@ describe('API3 SEARCH', function() { * Get document detail for futher processing */ self.get = function get (identifier, done) { - self.instance.get(`${self.url}/${identifier}?token=${self.token.read}`) + self.instance.get(`${self.url}/${identifier}`, self.jwt.read) .expect(200) .end((err, res) => { should.not.exist(err); @@ -35,7 +35,7 @@ describe('API3 SEARCH', function() { */ self.create = (doc) => new Promise((resolve) => { doc.identifier = opTools.calculateIdentifier(doc); - self.instance.post(`${self.url}?token=${self.token.all}`) + self.instance.post(`${self.url}`, self.jwt.all) .send(doc) .end((err) => { should.not.exist(err); @@ -52,12 +52,14 @@ describe('API3 SEARCH', function() { self.env = self.instance.env; self.url = '/api/v3/entries'; - let authResult = await authSubject(self.instance.ctx.authorization.storage); + let authResult = await authSubject(self.instance.ctx.authorization.storage, [ + 'read', + 'all' + ], self.instance.app); self.subject = authResult.subject; - self.token = authResult.token; - self.urlToken = `${self.url}?token=${self.token.read}`; - self.urlTest = `${self.urlToken}&srvModified$gte=${self.testStarted.getTime()}`; + self.jwt = authResult.jwt; + self.urlTest = `${self.url}?srvModified$gte=${self.testStarted.getTime()}`; const promises = testConst.SAMPLE_ENTRIES.map(doc => self.create(doc)); self.docs = await Promise.all(promises); @@ -80,7 +82,7 @@ describe('API3 SEARCH', function() { it('should not found not existing collection', async () => { - let res = await self.instance.get(`/api/v3/NOT_EXIST?token=${self.url}`) + let res = await self.instance.get(`/api/v3/NOT_EXIST`, self.jwt.read) .send(self.validDoc) .expect(404); @@ -90,7 +92,7 @@ describe('API3 SEARCH', function() { it('should found at least 10 documents', async () => { - let res = await self.instance.get(self.urlToken) + let res = await self.instance.get(self.url, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -99,7 +101,7 @@ describe('API3 SEARCH', function() { it('should found at least 10 documents from test start', async () => { - let res = await self.instance.get(self.urlTest) + let res = await self.instance.get(self.urlTest, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -108,7 +110,7 @@ describe('API3 SEARCH', function() { it('should reject invalid limit - not a number', async () => { - let res = await self.instance.get(`${self.urlToken}&limit=INVALID`) + let res = await self.instance.get(`${self.url}?limit=INVALID`, self.jwt.read) .expect(400); res.body.status.should.equal(400); @@ -118,7 +120,7 @@ describe('API3 SEARCH', function() { it('should reject invalid limit - negative number', async () => { - let res = await self.instance.get(`${self.urlToken}&limit=-1`) + let res = await self.instance.get(`${self.url}?limit=-1`, self.jwt.read) .expect(400); res.body.status.should.equal(400); @@ -128,7 +130,7 @@ describe('API3 SEARCH', function() { it('should reject invalid limit - zero', async () => { - let res = await self.instance.get(`${self.urlToken}&limit=0`) + let res = await self.instance.get(`${self.url}?limit=0`, self.jwt.read) .expect(400); res.body.status.should.equal(400); @@ -138,7 +140,7 @@ describe('API3 SEARCH', function() { it('should accept valid limit', async () => { - let res = await self.instance.get(`${self.urlToken}&limit=3`) + let res = await self.instance.get(`${self.url}?limit=3`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -147,7 +149,7 @@ describe('API3 SEARCH', function() { it('should reject invalid skip - not a number', async () => { - let res = await self.instance.get(`${self.urlToken}&skip=INVALID`) + let res = await self.instance.get(`${self.url}?skip=INVALID`, self.jwt.read) .expect(400); res.body.status.should.equal(400); @@ -157,7 +159,7 @@ describe('API3 SEARCH', function() { it('should reject invalid skip - negative number', async () => { - let res = await self.instance.get(`${self.urlToken}&skip=-5`) + let res = await self.instance.get(`${self.url}?skip=-5`, self.jwt.read) .expect(400); res.body.status.should.equal(400); @@ -167,7 +169,7 @@ describe('API3 SEARCH', function() { it('should reject both sort and sort$desc', async () => { - let res = await self.instance.get(`${self.urlToken}&sort=date&sort$desc=created_at`) + let res = await self.instance.get(`${self.url}?sort=date&sort$desc=created_at`, self.jwt.read) .expect(400); res.body.status.should.equal(400); @@ -177,7 +179,7 @@ describe('API3 SEARCH', function() { it('should sort well by date field', async () => { - let res = await self.instance.get(`${self.urlTest}&sort=date`) + let res = await self.instance.get(`${self.urlTest}&sort=date`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -185,7 +187,7 @@ describe('API3 SEARCH', function() { const length = ascending.length; length.should.be.aboveOrEqual(self.docs.length); - res = await self.instance.get(`${self.urlTest}&sort$desc=date`) + res = await self.instance.get(`${self.urlTest}&sort$desc=date`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -203,14 +205,14 @@ describe('API3 SEARCH', function() { it('should skip documents', async () => { - let res = await self.instance.get(`${self.urlToken}&sort=date&limit=8`) + let res = await self.instance.get(`${self.url}?sort=date&limit=8`, self.jwt.read) .expect(200); res.body.status.should.equal(200); const fullDocs = res.body.result; fullDocs.length.should.equal(8); - res = await self.instance.get(`${self.urlToken}&sort=date&skip=3&limit=5`) + res = await self.instance.get(`${self.url}?sort=date&skip=3&limit=5`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -224,7 +226,7 @@ describe('API3 SEARCH', function() { it('should project selected fields', async () => { - let res = await self.instance.get(`${self.urlToken}&fields=date,app,subject`) + let res = await self.instance.get(`${self.url}?fields=date,app,subject`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -236,7 +238,7 @@ describe('API3 SEARCH', function() { it('should project all fields', async () => { - let res = await self.instance.get(`${self.urlToken}&fields=_all`) + let res = await self.instance.get(`${self.url}?fields=_all`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -254,7 +256,7 @@ describe('API3 SEARCH', function() { const apiApp = self.instance.ctx.apiApp , limitBackup = apiApp.get('API3_MAX_LIMIT'); apiApp.set('API3_MAX_LIMIT', 5); - let res = await self.instance.get(`${self.urlToken}&limit=10`) + let res = await self.instance.get(`${self.url}?limit=10`, self.jwt.read) .expect(400); res.body.status.should.equal(400); @@ -267,7 +269,7 @@ describe('API3 SEARCH', function() { const apiApp = self.instance.ctx.apiApp , limitBackup = apiApp.get('API3_MAX_LIMIT'); apiApp.set('API3_MAX_LIMIT', 5); - let res = await self.instance.get(`${self.urlToken}`) + let res = await self.instance.get(`${self.url}`, self.jwt.read) .expect(200); res.body.status.should.equal(200); diff --git a/tests/api3.security.test.js b/tests/api3.security.test.js index df0928ffe9c..4899081fa2f 100644 --- a/tests/api3.security.test.js +++ b/tests/api3.security.test.js @@ -3,7 +3,6 @@ const request = require('supertest') , apiConst = require('../lib/api3/const.json') - , moment = require('moment') ; require('should'); @@ -20,9 +19,13 @@ describe('Security of REST API3', function() { self.http = await instance.create({ useHttps: false }); self.https = await instance.create({ }); - let authResult = await authSubject(self.https.ctx.authorization.storage); + let authResult = await authSubject(self.https.ctx.authorization.storage, [ + 'denied', + 'read', + 'delete' + ], self.https.app); self.subject = authResult.subject; - self.token = authResult.token; + self.jwt = authResult.jwt; }); @@ -35,7 +38,6 @@ describe('Security of REST API3', function() { it('should require token', async () => { let res = await request(self.https.baseUrl) .get('/api/v3/test') - .set('Date', new Date().toUTCString()) .expect(401); res.body.status.should.equal(401); @@ -45,19 +47,19 @@ describe('Security of REST API3', function() { it('should require valid token', async () => { let res = await request(self.https.baseUrl) - .get('/api/v3/test?token=invalid_token') - .set('Date', new Date().toUTCString()) + .get('/api/v3/test') + .set('Authorization', 'Bearer invalid_token') .expect(401); res.body.status.should.equal(401); - res.body.message.should.equal(apiConst.MSG.HTTP_401_MISSING_OR_BAD_TOKEN); + res.body.message.should.equal(apiConst.MSG.HTTP_401_BAD_TOKEN); }); it('should deny subject denied', async () => { let res = await request(self.https.baseUrl) - .get('/api/v3/test?token=' + self.subject.denied.accessToken) - .set('Date', new Date().toUTCString()) + .get('/api/v3/test') + .set('Authorization', `Bearer ${self.jwt.denied}`) .expect(403); res.body.status.should.equal(403); @@ -67,37 +69,10 @@ describe('Security of REST API3', function() { it('should allow subject with read permission', async () => { await request(self.https.baseUrl) - .get('/api/v3/test?token=' + self.token.read) - .set('Date', new Date().toUTCString()) - .expect(200); - }); - - - it('should accept valid now - epoch in ms', async () => { - await request(self.https.baseUrl) - .get(`/api/v3/test?token=${self.token.read}&now=${moment().valueOf()}`) - .expect(200); - }); - - - it('should accept valid now - epoch in seconds', async () => { - await request(self.https.baseUrl) - .get(`/api/v3/test?token=${self.token.read}&now=${moment().unix()}`) + .get('/api/v3/test', self.jwt.read) + .set('Authorization', `Bearer ${self.jwt.read}`) .expect(200); }); - it('should accept valid now - ISO 8601', async () => { - await request(self.https.baseUrl) - .get(`/api/v3/test?token=${self.token.read}&now=${moment().toISOString()}`) - .expect(200); - }); - - - it('should accept valid now - RFC 2822', async () => { - await request(self.https.baseUrl) - .get(`/api/v3/test?token=${self.token.read}&now=${moment().utc().format('ddd, DD MMM YYYY HH:mm:ss [GMT]')}`) - .expect(200); - }); - }); diff --git a/tests/api3.socket.test.js b/tests/api3.socket.test.js index 8c63486bba1..fe71b04e019 100644 --- a/tests/api3.socket.test.js +++ b/tests/api3.socket.test.js @@ -37,10 +37,15 @@ describe('Socket.IO in REST API3', function() { self.urlResource = self.urlCol + '/' + self.identifier; self.urlHistory = self.urlCol + '/history'; - let authResult = await authSubject(self.instance.ctx.authorization.storage); + let authResult = await authSubject(self.instance.ctx.authorization.storage, [ + 'create', + 'update', + 'delete' + ], self.instance.app); self.subject = authResult.subject; - self.token = authResult.token; + self.jwt = authResult.jwt; + self.accessToken = authResult.accessToken; self.socket = self.instance.clientSocket; }); @@ -72,7 +77,7 @@ describe('Socket.IO in REST API3', function() { it('should not subscribe by subject with no rights', done => { - self.socket.emit('subscribe', { accessToken: self.token.denied }, function (data) { + self.socket.emit('subscribe', { accessToken: self.accessToken.denied }, function (data) { data.success.should.not.equal(true); data.message.should.equal(apiConst.MSG.SOCKET_UNAUTHORIZED_TO_ANY); done(); @@ -84,7 +89,7 @@ describe('Socket.IO in REST API3', function() { const cols = ['entries', 'treatments']; self.socket.emit('subscribe', { - accessToken: self.token.all, + accessToken: self.accessToken.all, collections: cols }, function (data) { data.success.should.equal(true); @@ -104,7 +109,7 @@ describe('Socket.IO in REST API3', function() { done(); }); - self.instance.post(`${self.urlCol}?token=${self.token.create}`) + self.instance.post(`${self.urlCol}`, self.jwt.create) .send(self.docOriginal) .expect(201) .end((err) => { @@ -126,7 +131,7 @@ describe('Socket.IO in REST API3', function() { done(); }); - self.instance.put(`${self.urlResource}?token=${self.token.update}`) + self.instance.put(`${self.urlResource}`, self.jwt.update) .send(self.docActual) .expect(200) .end((err) => { @@ -150,7 +155,7 @@ describe('Socket.IO in REST API3', function() { done(); }); - self.instance.patch(`${self.urlResource}?token=${self.token.update}`) + self.instance.patch(`${self.urlResource}`, self.jwt.update) .send({ 'carbs': self.docActual.carbs, 'insulin': self.docActual.insulin }) .expect(200) .end((err) => { @@ -167,7 +172,7 @@ describe('Socket.IO in REST API3', function() { done(); }); - self.instance.delete(`${self.urlResource}?token=${self.token.delete}`) + self.instance.delete(`${self.urlResource}`, self.jwt.delete) .expect(200) .end((err) => { should.not.exist(err); diff --git a/tests/api3.update.test.js b/tests/api3.update.test.js index 7c28dbb2205..14f4fb877fb 100644 --- a/tests/api3.update.test.js +++ b/tests/api3.update.test.js @@ -29,7 +29,7 @@ describe('API3 UPDATE', function() { * Get document detail for futher processing */ self.get = async function get (identifier) { - let res = await self.instance.get(`${self.url}/${identifier}?token=${self.token.read}`) + let res = await self.instance.get(`${self.url}/${identifier}`, self.jwt.read) .expect(200); res.body.status.should.equal(200); @@ -45,11 +45,16 @@ describe('API3 UPDATE', function() { self.col = 'treatments' self.url = `/api/v3/${self.col}`; - let authResult = await authSubject(self.instance.ctx.authorization.storage); + let authResult = await authSubject(self.instance.ctx.authorization.storage, [ + 'read', + 'update', + 'delete', + 'all' + ], self.instance.app); self.subject = authResult.subject; - self.token = authResult.token; - self.urlToken = `${self.url}/${self.validDoc.identifier}?token=${self.token.update}` + self.jwt = authResult.jwt; + self.urlIdent = `${self.url}/${self.validDoc.identifier}` self.cache = self.instance.cacheMonitor; }); @@ -79,7 +84,7 @@ describe('API3 UPDATE', function() { it('should not found not existing collection', async () => { - let res = await self.instance.put(`/api/v3/NOT_EXIST?token=${self.url}`) + let res = await self.instance.put(`/api/v3/NOT_EXIST`, self.jwt.update) .send(self.validDoc) .expect(404); @@ -88,7 +93,7 @@ describe('API3 UPDATE', function() { it('should require update permission for upsert', async () => { - let res = await self.instance.put(`${self.url}/${self.validDoc.identifier}?token=${self.token.update}`) + let res = await self.instance.put(`${self.url}/${self.validDoc.identifier}`, self.jwt.update) .send(self.validDoc) .expect(403); @@ -98,7 +103,7 @@ describe('API3 UPDATE', function() { it('should upsert not existing document', async () => { - let res = await self.instance.put(`${self.url}/${self.validDoc.identifier}?token=${self.token.all}`) + let res = await self.instance.put(`${self.url}/${self.validDoc.identifier}`, self.jwt.all) .send(self.validDoc) .expect(201); @@ -108,7 +113,7 @@ describe('API3 UPDATE', function() { const lastModified = new Date(res.headers['last-modified']).getTime(); // Last-Modified has trimmed milliseconds - let body = await self.get(self.validDoc.identifier); + let body = await self.get(self.validDoc.identifier, self.jwt.read); body.should.containEql(self.validDoc); should.not.exist(body.modifiedBy); @@ -123,7 +128,7 @@ describe('API3 UPDATE', function() { self.validDoc.carbs = 10; delete self.validDoc.insulin; - let res = await self.instance.put(self.urlToken) + let res = await self.instance.put(self.urlIdent, self.jwt.update) .send(self.validDoc) .expect(200); @@ -132,7 +137,7 @@ describe('API3 UPDATE', function() { const lastModified = new Date(res.headers['last-modified']).getTime(); // Last-Modified has trimmed milliseconds - let body = await self.get(self.validDoc.identifier); + let body = await self.get(self.validDoc.identifier, self.jwt.read); body.should.containEql(self.validDoc); should.not.exist(body.insulin); should.not.exist(body.modifiedBy); @@ -147,7 +152,7 @@ describe('API3 UPDATE', function() { const doc = Object.assign({}, self.validDoc, { carbs: 11 }); - let res = await self.instance.put(self.urlToken) + let res = await self.instance.put(self.urlIdent, self.jwt.update) .set('If-Unmodified-Since', new Date(new Date().getTime() + 1000).toUTCString()) .send(doc) .expect(200); @@ -155,7 +160,7 @@ describe('API3 UPDATE', function() { res.body.status.should.equal(200); self.cache.nextShouldEql(self.col, doc) - let body = await self.get(self.validDoc.identifier); + let body = await self.get(self.validDoc.identifier, self.jwt.read); body.should.containEql(doc); }); @@ -167,20 +172,20 @@ describe('API3 UPDATE', function() { let body = await self.get(doc.identifier); self.validDoc = body; - let res = await self.instance.put(self.urlToken) + let res = await self.instance.put(self.urlIdent, self.jwt.update) .set('If-Unmodified-Since', new Date(new Date(body.srvModified).getTime() - 1000).toUTCString()) .send(doc) .expect(412); res.body.status.should.equal(412); - body = await self.get(doc.identifier); + body = await self.get(doc.identifier, self.jwt.read); body.should.eql(self.validDoc); }); it('should reject date alteration', async () => { - let res = await self.instance.put(self.urlToken) + let res = await self.instance.put(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { date: self.validDoc.date + 10000 })) .expect(400); @@ -190,7 +195,7 @@ describe('API3 UPDATE', function() { it('should reject utcOffset alteration', async () => { - let res = await self.instance.put(self.urlToken) + let res = await self.instance.put(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { utcOffset: self.utcOffset - 120 })) .expect(400); @@ -200,7 +205,7 @@ describe('API3 UPDATE', function() { it('should reject eventType alteration', async () => { - let res = await self.instance.put(self.urlToken) + let res = await self.instance.put(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { eventType: 'MODIFIED' })) .expect(400); @@ -210,7 +215,7 @@ describe('API3 UPDATE', function() { it('should reject device alteration', async () => { - let res = await self.instance.put(self.urlToken) + let res = await self.instance.put(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { device: 'MODIFIED' })) .expect(400); @@ -220,7 +225,7 @@ describe('API3 UPDATE', function() { it('should reject app alteration', async () => { - let res = await self.instance.put(self.urlToken) + let res = await self.instance.put(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { app: 'MODIFIED' })) .expect(400); @@ -230,7 +235,7 @@ describe('API3 UPDATE', function() { it('should reject srvCreated alteration', async () => { - let res = await self.instance.put(self.urlToken) + let res = await self.instance.put(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { srvCreated: self.validDoc.date - 10000 })) .expect(400); @@ -240,7 +245,7 @@ describe('API3 UPDATE', function() { it('should reject subject alteration', async () => { - let res = await self.instance.put(self.urlToken) + let res = await self.instance.put(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { subject: 'MODIFIED' })) .expect(400); @@ -250,7 +255,7 @@ describe('API3 UPDATE', function() { it('should reject srvModified alteration', async () => { - let res = await self.instance.put(self.urlToken) + let res = await self.instance.put(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { srvModified: self.validDoc.date - 100000 })) .expect(400); @@ -260,7 +265,7 @@ describe('API3 UPDATE', function() { it('should reject modifiedBy alteration', async () => { - let res = await self.instance.put(self.urlToken) + let res = await self.instance.put(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { modifiedBy: 'MODIFIED' })) .expect(400); @@ -270,7 +275,7 @@ describe('API3 UPDATE', function() { it('should reject isValid alteration', async () => { - let res = await self.instance.put(self.urlToken) + let res = await self.instance.put(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { isValid: false })) .expect(400); @@ -282,7 +287,7 @@ describe('API3 UPDATE', function() { it('should ignore identifier alteration in body', async () => { self.validDoc = await self.get(self.validDoc.identifier); - let res = await self.instance.put(self.urlToken) + let res = await self.instance.put(self.urlIdent, self.jwt.update) .send(Object.assign({}, self.validDoc, { identifier: 'MODIFIED' })) .expect(200); @@ -293,13 +298,13 @@ describe('API3 UPDATE', function() { it('should not update deleted document', async () => { - let res = await self.instance.delete(`${self.url}/${self.validDoc.identifier}?token=${self.token.delete}`) + let res = await self.instance.delete(self.urlIdent, self.jwt.delete) .expect(200); res.body.status.should.equal(200); self.cache.nextShouldDeleteLast(self.col) - res = await self.instance.put(self.urlToken) + res = await self.instance.put(self.urlIdent, self.jwt.update) .send(self.validDoc) .expect(410); diff --git a/tests/fixtures/api3/authSubject.js b/tests/fixtures/api3/authSubject.js index a9a79fb70eb..5d0385477a1 100644 --- a/tests/fixtures/api3/authSubject.js +++ b/tests/fixtures/api3/authSubject.js @@ -1,6 +1,9 @@ 'use strict'; -const _ = require('lodash'); +const _ = require('lodash') + , request = require('supertest') + ; +require('should'); function createRole (authStorage, name, permissions) { @@ -57,7 +60,37 @@ function createTestSubject (authStorage, subjectName, roles) { } -async function authSubject (authStorage) { +async function initJwts (accessToken, tokensNeeded, app) { + const jwt = {} + if (!_.isArray(tokensNeeded) || !app) + return jwt; + + for (const tokenNeeded of tokensNeeded) { + jwt[tokenNeeded] = await new Promise((resolve, reject) => { + try { + const authToken = accessToken[tokenNeeded]; + + request(app) + .get(`/api/v2/authorization/request/${authToken}`) + .expect(200) + .end(function(err, res) { + if (err) + reject(err); + + resolve(res.body.token); + }); + } + catch (e) { + reject(e) + } + }) + } + + return jwt; +} + + +async function authSubject (authStorage, tokensNeeded, app) { await createRole(authStorage, 'admin', '*'); await createRole(authStorage, 'readable', '*:*:read'); @@ -83,7 +116,7 @@ async function authSubject (authStorage) { noneRole: await createTestSubject(authStorage, 'noneRole', ['noneRole']) }; - const token = { + const accessToken = { all: subject.apiAll.accessToken, admin: subject.apiAdmin.accessToken, create: subject.apiCreate.accessToken, @@ -97,7 +130,9 @@ async function authSubject (authStorage) { noneRole: subject.noneRole.accessToken }; - return {subject, token}; + const jwt = await initJwts(accessToken, tokensNeeded, app); + + return {subject, accessToken, jwt}; } -module.exports = authSubject; \ No newline at end of file +module.exports = authSubject; diff --git a/tests/fixtures/api3/instance.js b/tests/fixtures/api3/instance.js index 358b42bb2c2..cbeaf6f97d0 100644 --- a/tests/fixtures/api3/instance.js +++ b/tests/fixtures/api3/instance.js @@ -41,17 +41,24 @@ function configure () { }; + function addJwt (req, jwt) { + return jwt + ? req.set('Authorization', `Bearer ${jwt}`) + : req; + } + + self.addSecuredOperations = function addSecuredOperations (instance) { - instance.get = (url) => request(instance.baseUrl).get(url); + instance.get = (url, jwt) => addJwt(request(instance.baseUrl).get(url), jwt); - instance.post = (url) => request(instance.baseUrl).post(url); + instance.post = (url, jwt) => addJwt(request(instance.baseUrl).post(url), jwt); - instance.put = (url) => request(instance.baseUrl).put(url); + instance.put = (url, jwt) => addJwt(request(instance.baseUrl).put(url), jwt); - instance.patch = (url) => request(instance.baseUrl).patch(url); + instance.patch = (url, jwt) => addJwt(request(instance.baseUrl).patch(url), jwt); - instance.delete = (url) => request(instance.baseUrl).delete(url); + instance.delete = (url, jwt) => addJwt(request(instance.baseUrl).delete(url), jwt); }; @@ -122,6 +129,7 @@ function configure () { } instance.app.use('/api/v3', instance.ctx.apiApp); + instance.app.use('/api/v2/authorization', instance.ctx.authorization.endpoints); const transport = useHttps ? https : http;