From 34cb0caa6410bc8d1467b66cad9439778c5134a8 Mon Sep 17 00:00:00 2001 From: PetrOndrusek Date: Mon, 10 Feb 2020 21:13:27 +0100 Subject: [PATCH 1/4] APIv3: isolating documents from tests (not allowing clashes of calculated identifiers) --- tests/api.alexa.test.js | 1 + tests/api3.create.test.js | 2 +- tests/api3.generic.workflow.test.js | 4 ++-- tests/api3.patch.test.js | 2 +- tests/api3.read.test.js | 2 +- tests/api3.update.test.js | 1 + 6 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/api.alexa.test.js b/tests/api.alexa.test.js index 8c2f5916cf6..440050eb2cc 100644 --- a/tests/api.alexa.test.js +++ b/tests/api.alexa.test.js @@ -7,6 +7,7 @@ const bodyParser = require('body-parser'); require('should'); describe('Alexa REST api', function ( ) { + this.timeout(10000); const apiRoot = require('../lib/api/root'); const api = require('../lib/api/'); before(function (done) { diff --git a/tests/api3.create.test.js b/tests/api3.create.test.js index ba0cce83ba3..5ea3ab1a869 100644 --- a/tests/api3.create.test.js +++ b/tests/api3.create.test.js @@ -16,7 +16,7 @@ describe('API3 CREATE', function() { self.validDoc = { date: (new Date()).getTime(), app: testConst.TEST_APP, - device: testConst.TEST_DEVICE, + device: testConst.TEST_DEVICE + ' API3 CREATE', eventType: 'Correction Bolus', insulin: 0.3 }; diff --git a/tests/api3.generic.workflow.test.js b/tests/api3.generic.workflow.test.js index f94238d6747..eea6a98495b 100644 --- a/tests/api3.generic.workflow.test.js +++ b/tests/api3.generic.workflow.test.js @@ -19,7 +19,7 @@ describe('Generic REST API3', function() { insulin: 1, date: (new Date()).getTime(), app: testConst.TEST_APP, - device: testConst.TEST_DEVICE + device: testConst.TEST_DEVICE + ' Generic REST API3' }; self.identifier = opTools.calculateIdentifier(self.docOriginal); self.docOriginal.identifier = self.identifier; @@ -43,7 +43,7 @@ describe('Generic REST API3', function() { }); - after(() => { + after(async () => { self.instance.ctx.bus.teardown(); }); diff --git a/tests/api3.patch.test.js b/tests/api3.patch.test.js index 3967e53cf12..36dccc94bfa 100644 --- a/tests/api3.patch.test.js +++ b/tests/api3.patch.test.js @@ -15,7 +15,7 @@ describe('API3 PATCH', function() { date: (new Date()).getTime(), utcOffset: -180, app: testConst.TEST_APP, - device: testConst.TEST_DEVICE, + device: testConst.TEST_DEVICE + ' API3 PATCH', eventType: 'Correction Bolus', insulin: 0.3 }; diff --git a/tests/api3.read.test.js b/tests/api3.read.test.js index de29b26d000..d9f73ebf13a 100644 --- a/tests/api3.read.test.js +++ b/tests/api3.read.test.js @@ -15,7 +15,7 @@ describe('API3 READ', function() { self.validDoc = { date: (new Date()).getTime(), app: testConst.TEST_APP, - device: testConst.TEST_DEVICE, + device: testConst.TEST_DEVICE + ' API3 READ', uploaderBattery: 58 }; self.validDoc.identifier = opTools.calculateIdentifier(self.validDoc); diff --git a/tests/api3.update.test.js b/tests/api3.update.test.js index 35995d27236..481827b05d6 100644 --- a/tests/api3.update.test.js +++ b/tests/api3.update.test.js @@ -17,6 +17,7 @@ describe('API3 UPDATE', function() { date: (new Date()).getTime(), utcOffset: -180, app: testConst.TEST_APP, + device: testConst.TEST_DEVICE + ' API3 UPDATE', eventType: 'Correction Bolus', insulin: 0.3 }; From 9b8e6b79f3de823c7715cbaa0a52ee377bccd180 Mon Sep 17 00:00:00 2001 From: PetrOndrusek Date: Mon, 10 Feb 2020 21:23:31 +0100 Subject: [PATCH 2/4] removing unused async keyword --- tests/api3.generic.workflow.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/api3.generic.workflow.test.js b/tests/api3.generic.workflow.test.js index eea6a98495b..7cfbc53f618 100644 --- a/tests/api3.generic.workflow.test.js +++ b/tests/api3.generic.workflow.test.js @@ -43,7 +43,7 @@ describe('Generic REST API3', function() { }); - after(async () => { + after(() => { self.instance.ctx.bus.teardown(); }); From 090ce25ad536ae8bc900411fac0f454f9c1f6bf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Ondr=C5=AF=C5=A1ek?= Date: Tue, 6 Oct 2020 11:10:59 +0200 Subject: [PATCH 3/4] fixing api v3 swagger and moving it to /api3-docs --- app.js | 5 +- lib/api3/doc/tutorial.md | 22 +- lib/api3/index.js | 8 +- lib/api3/swagger.js | 41 - lib/api3/swagger.json | 2251 ++++++++++++++++++++++++++++++++++++++ tests/api3.basic.test.js | 9 - 6 files changed, 2271 insertions(+), 65 deletions(-) delete mode 100644 lib/api3/swagger.js create mode 100644 lib/api3/swagger.json diff --git a/app.js b/app.js index c6528819bb9..90bb372a315 100644 --- a/app.js +++ b/app.js @@ -276,9 +276,12 @@ function create (env, ctx) { // API docs const swaggerUi = require('swagger-ui-express'); + const swaggerUseSchema = schema => (...args) => swaggerUi.setup(schema)(...args); const swaggerDocument = require('./swagger.json'); + const swaggerDocumentApiV3 = require('./lib/api3/swagger.json'); - app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); + app.use('/api-docs', swaggerUi.serve, swaggerUseSchema(swaggerDocument)); + app.use('/api3-docs', swaggerUi.serve, swaggerUseSchema(swaggerDocumentApiV3)); app.use('/swagger-ui-dist', (req, res) => { res.redirect(307, '/api-docs'); diff --git a/lib/api3/doc/tutorial.md b/lib/api3/doc/tutorial.md index 3d8c656dfbd..d69e95ef4a4 100644 --- a/lib/api3/doc/tutorial.md +++ b/lib/api3/doc/tutorial.md @@ -11,7 +11,7 @@ Each NS instance with API v3 contains self-included OpenAPI specification at [/a --- ### VERSION -[VERSION](https://nsapiv3.herokuapp.com/api/v3/swagger-ui-dist/#/other/get_version) operation gets you basic information about software packages versions. +[VERSION](https://nsapiv3.herokuapp.com/api3-docs/#/other/get_version) operation gets you basic information about software packages versions. It is public (there is no need to add authorization parameters/headers). Sample GET `/version` client code (to get actual versions): @@ -38,7 +38,7 @@ Sample result: --- ### STATUS -[STATUS](https://nsapiv3.herokuapp.com/api/v3/swagger-ui-dist/#/other/get_status) operation gets you basic information about software packages versions. +[STATUS](https://nsapiv3.herokuapp.com/api3-docs/#/other/get_status) operation gets you basic information about software packages versions. It is public (there is no need to add authorization parameters/headers). Sample GET `/status` client code (to get my actual permissions): @@ -75,7 +75,7 @@ Sample result: --- ### SEARCH -[SEARCH](https://nsapiv3insecure.herokuapp.com/api/v3/swagger-ui-dist/index.html#/generic/SEARCH) operation filters, sorts, paginates and projects documents from the collection. +[SEARCH](https://nsapiv3insecure.herokuapp.com/api3-docs/#/generic/SEARCH) operation filters, sorts, paginates and projects documents from the collection. Sample GET `/entries` client code (to retrieve last 3 BG values): ```javascript @@ -110,7 +110,7 @@ Sample result: --- ### CREATE -[CREATE](https://nsapiv3.herokuapp.com/api/v3/swagger-ui-dist/#/generic/post__collection_) operation inserts a new document into the collection. +[CREATE](https://nsapiv3.herokuapp.com/api3-docs/#/generic/post__collection_) operation inserts a new document into the collection. Sample POST `/treatments` client code: ```javascript @@ -140,7 +140,7 @@ Sample result: --- ### READ -[READ](https://nsapiv3.herokuapp.com/api/v3/swagger-ui-dist/#/generic/get__collection___identifier_) operation retrieves you a single document from the collection by its identifier. +[READ](https://nsapiv3.herokuapp.com/api3-docs/#/generic/get__collection___identifier_) operation retrieves you a single document from the collection by its identifier. Sample GET `/treatments/{identifier}` client code: ```javascript @@ -172,7 +172,7 @@ Sample result: --- ### LAST MODIFIED -[LAST MODIFIED](https://nsapiv3insecure.herokuapp.com/api/v3/swagger-ui-dist/index.html#/other/LAST-MODIFIED) operation finds the date of last modification for each collection. +[LAST MODIFIED](https://nsapiv3insecure.herokuapp.com/api3-docs/#/other/LAST-MODIFIED) operation finds the date of last modification for each collection. Sample GET `/lastModified` client code (to get latest modification dates): ```javascript @@ -199,7 +199,7 @@ Sample result: --- ### UPDATE -[UPDATE](https://nsapiv3insecure.herokuapp.com/api/v3/swagger-ui-dist/index.html#/generic/put__collection___identifier_) operation updates existing document in the collection. +[UPDATE](https://nsapiv3insecure.herokuapp.com/api3-docs/#/generic/put__collection___identifier_) operation updates existing document in the collection. Sample PUT `/treatments/{identifier}` client code (to update `insulin` from 0.3 to 0.4): ```javascript @@ -231,7 +231,7 @@ Sample result: --- ### PATCH -[PATCH](https://nsapiv3insecure.herokuapp.com/api/v3/swagger-ui-dist/index.html#/generic/patch__collection___identifier_) operation partially updates existing document in the collection. +[PATCH](https://nsapiv3insecure.herokuapp.com/api3-docs/#/generic/patch__collection___identifier_) operation partially updates existing document in the collection. Sample PATCH `/treatments/{identifier}` client code (to update `insulin` from 0.4 to 0.5): ```javascript @@ -259,7 +259,7 @@ Sample result: --- ### DELETE -[DELETE](https://nsapiv3insecure.herokuapp.com/api/v3/swagger-ui-dist/index.html#/generic/delete__collection___identifier_) operation deletes existing document from the collection. +[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): ```javascript @@ -282,7 +282,7 @@ Sample result: --- ### HISTORY -[HISTORY](https://nsapiv3insecure.herokuapp.com/api/v3/swagger-ui-dist/index.html#/generic/HISTORY2) operation queries all changes since the timestamp. +[HISTORY](https://nsapiv3insecure.herokuapp.com/api3-docs/#/generic/HISTORY2) operation queries all changes since the timestamp. Sample HISTORY `/treatments/history/{lastModified}` client code: ```javascript @@ -326,4 +326,4 @@ Sample result: } ] ``` -Notice the `"isValid":false` field marking the deletion of the document. \ No newline at end of file +Notice the `"isValid":false` field marking the deletion of the document. diff --git a/lib/api3/index.js b/lib/api3/index.js index 4bfe07a35fe..bff0899536f 100644 --- a/lib/api3/index.js +++ b/lib/api3/index.js @@ -7,7 +7,6 @@ const express = require('express') , apiConst = require('./const.json') , security = require('./security') , genericSetup = require('./generic/setup') - , swaggerSetup = require('./swagger') ; function configure (env, ctx) { @@ -35,7 +34,7 @@ function configure (env, ctx) { self.setupApiEnvironment = function setupApiEnvironment () { - + app.use(bodyParser.json({ limit: 1048576 * 50 }), function errorHandler (err, req, res, next) { @@ -100,7 +99,10 @@ function configure (env, ctx) { self.setupApiEnvironment(); genericSetup(ctx, env, app); self.setupApiRoutes(); - swaggerSetup(app); + + app.use('/swagger-ui-dist', (req, res) => { + res.redirect(307, '../../../api3-docs'); + }); ctx.storageSocket = new StorageSocket(app, env, ctx); diff --git a/lib/api3/swagger.js b/lib/api3/swagger.js deleted file mode 100644 index ff965061c87..00000000000 --- a/lib/api3/swagger.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -const express = require('express') - , fs = require('fs') - ; - - -function setupSwaggerUI (app) { - - const serveSwaggerDef = function serveSwaggerDef (req, res) { - res.sendFile(__dirname + '/swagger.yaml'); - }; - app.get('/swagger', serveSwaggerDef); - - const swaggerUiAssetPath = require('swagger-ui-dist').getAbsoluteFSPath(); - const swaggerFiles = express.static(swaggerUiAssetPath); - - const urlRegex = /url: "[^"]*",/; - - const patchIndex = function patchIndex (req, res) { - const indexContent = fs.readFileSync(`${swaggerUiAssetPath}/index.html`) - .toString() - .replace(urlRegex, 'url: "../swagger.yaml",'); - res.send(indexContent); - }; - - app.get('/swagger-ui-dist', function getSwaggerRoot (req, res) { - let targetUrl = req.originalUrl; - if (!targetUrl.endsWith('/')) { - targetUrl += '/'; - } - targetUrl += 'index.html'; - res.redirect(targetUrl); - }); - app.get('/swagger-ui-dist/index.html', patchIndex); - - app.use('/swagger-ui-dist', swaggerFiles); -} - - -module.exports = setupSwaggerUI; \ No newline at end of file diff --git a/lib/api3/swagger.json b/lib/api3/swagger.json new file mode 100644 index 00000000000..b20bb0e6761 --- /dev/null +++ b/lib/api3/swagger.json @@ -0,0 +1,2251 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Nightscout API", + "description": "Nightscout API v3 is a component of cgm-remote-monitor project. It aims to provide lightweight, secured and HTTP REST compliant interface for your T1D treatment data exchange.\n\nAPI v3 uses these environment variables, among other things:\n- Security switch (optional, default = `true`)
API3_SECURITY_ENABLE=true
You can turn the whole security mechanism off, e.g. for debugging or development purposes, but this should never be set to false in production.\n\n- Number of minutes of acceptable time skew between client's and server's clock (optional, default = 5)
API3_TIME_SKEW_TOLERANCE=5
This security parameter is used for preventing anti-replay attacks, specifically when checking the time from `Date` header.\n\n- Maximum limit count of documents retrieved from single query
API3_MAX_LIMIT=1000
\n\n- Autopruning of obsolete documents (optional, default is only `DEVICESTATUS`=60)
API3_AUTOPRUNE_DEVICESTATUS=60\nAPI3_AUTOPRUNE_ENTRIES=365\nAPI3_AUTOPRUNE_TREATMENTS=120 
You can specify for which collections autopruning will be activated and length of retention period in days, e.g. \"Hold 60 days of devicestatus, automatically delete older documents, hold 365 days of treatments and entries, automatically delete older documents.\"\n\n- Fallback deduplication switch (optional, default = true)
API3_DEDUP_FALLBACK_ENABLED=true
API3 uses the `identifier` field for document identification and mutual distinction within a single collection. There is automatic deduplication implemented matching the equal `identifier` field. E.g. `CREATE` operation for document having the same `identifier` as another one existing in the database is automatically transformed into `UPDATE` operation of the document found in the database.\nDocuments not created via API v3 usually does not have any `identifier` field, but we would like to have some form of deduplication for them, too. This fallback deduplication is turned on by having set `API3_DEDUP_FALLBACK_ENABLED` to `true`. When searching the collection in database, the document is found to be a duplicate only when either he has equal `identifier` or he has no `identifier` and meets:
`devicestatus` collection: equal combination of `created_at` and `device`\n`entries` collection:      equal combination of `date` and `type`\n`food` collection:         equal `created_at`\n`profile` collection:      equal `created_at`\n`treatments` collection:   equal combination of `created_at` and `eventType` 
\n\n- Fallback switch for adding `created_at` field along the `date` field (optional, default = true)
API3_CREATED_AT_FALLBACK_ENABLED=true
Standard APIv3 document model uses only `date` field for storing a timestamp of the event recorded by the document. But there is a fallback option to fill `created_at` field as well automatically on each insert/update, just to keep all older components working.", + "contact": { + "name": "NS development discussion channel", + "url": "https://gitter.im/nightscout/public" + }, + "license": { + "name": "AGPL 3", + "url": "https://www.gnu.org/licenses/agpl.txt" + }, + "version": "3.0.1" + }, + "servers": [ + { + "url": "/api/v3" + } + ], + "tags": [ + { + "name": "generic", + "description": "Generic operations with each database collection (devicestatus, entries, food, profile, settings, treatments)" + }, + { + "name": "other", + "description": "All other various operations" + } + ], + "paths": { + "/{collection}": { + "get": { + "tags": [ + "generic" + ], + "summary": "SEARCH: Search documents from the collection", + "description": "General search operation through documents of one collection, matching the specified filtering criteria. You can apply:\n\n1) filtering - combining any number of filtering parameters\n\n2) ordering - using `sort` or `sort$desc` parameter\n\n3) paging - using `limit` and `skip` parameters\n\nWhen there is no document matching the filtering criteria, HTTP status 204 is returned with empty response content. Otherwise HTTP 200 code is returned with JSON array of matching documents as a response content.\n\nThis operation requires `read` permission for the API and the collection (e.g. `*:*:read`, `api:*:read`, `*:treatments:read`, `api:treatments:read`).\n\nThe only exception is the `settings` collection which requires `admin` permission (`api:settings:admin`), because the settings of each application should be isolated and kept secret. You need to know the concrete identifier to access the app's settings.", + "operationId": "SEARCH", + "parameters": [ + { + "name": "collection", + "in": "path", + "description": "Collection to which the operation is targeted", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "$ref": "#/components/schemas/paramCollection" + } + }, + { + "name": "Date", + "in": "header", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + { + "name": "now", + "in": "query", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "integer", + "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": "filter_parameters", + "in": "query", + "description": "Any number of filtering operators.\n\nEach filtering operator has name like `$`, e.g. `carbs$gt=2` which represents filtering rule \"The field carbs must be present and greater than 2\".\n\nYou can choose from operators:\n\n`eq`=equals, `insulin$eq=1.5`\n\n`ne`=not equals, `insulin$ne=1.5`\n\n`gt`=greater than, `carbs$gt=30`\n\n`gte`=greater than or equal, `carbs$gte=30`\n\n`lt`=less than, `carbs$lt=30`\n\n`lte`=less than or equal, `carbs$lte=30`\n\n`in`=in specified set, `type$in=sgv|mbg|cal`\n\n`nin`=not in specified set, `eventType$nin=Temp%20Basal|Temporary%20Target`\n\n`re`=regex pattern, `eventType$re=Temp.%2A`\n\nWhen filtering by field `date`, `created_at`, `srvModified` or `srvCreated`, you can choose from three input formats\n- Unix epoch in milliseconds (1525383610088)\n- Unix epoch in seconds (1525383610)\n- ISO 8601 with optional timezone ('2018-05-03T21:40:10.088Z' or '2018-05-03T23:40:10.088+02:00')\n\nThe date is always queried in a normalized form - UTC with zero offset and with the correct format (1525383610088 for `date`, '2018-05-03T21:40:10.088Z' for `created_at`).", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "Field name by which the sorting of documents is performed. This parameter cannot be combined with `sort$desc` parameter.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string" + } + }, + { + "name": "sort$desc", + "in": "query", + "description": "Field name by which the descending (reverse) sorting of documents is performed. This parameter cannot be combined with `sort` parameter.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of documents to get in result array", + "required": false, + "style": "form", + "explode": true, + "schema": { + "minimum": 1, + "type": "integer", + "example": 100 + } + }, + { + "name": "skip", + "in": "query", + "description": "Number of documents to skip from collection query before loading them into result array (used for pagination)", + "required": false, + "style": "form", + "explode": true, + "schema": { + "minimum": 0, + "type": "integer", + "example": 0, + "default": 0 + } + }, + { + "name": "fields", + "in": "query", + "description": "A chosen set of fields to return in response. Either you can enumerate specific fields of interest or use the predefined set. Sample parameter values:\n\n_all: All fields will be returned (default value)\n\ndate,insulin: Only fields `date` and `insulin` will be returned", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string", + "default": "_all" + }, + "examples": { + "all": { + "summary": "All fields will be returned (default behaviour)", + "value": "_all" + }, + "customSet": { + "summary": "Only fields date and insulin will be returned", + "value": "date,insulin" + } + } + } + ], + "responses": { + "200": { + "description": "Successful operation returning array of documents matching the filtering criteria", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DocumentArray" + } + }, + "text/csv": { + "schema": { + "$ref": "#/components/schemas/DocumentArray" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/DocumentArray" + } + } + } + }, + "204": { + "description": "Successful operation - no documents matching the filtering criteria" + }, + "400": { + "description": "The request is malformed. There may be some required parameters missing or there are unrecognized parameters present." + }, + "401": { + "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy." + }, + "403": { + "description": "Insecure HTTP scheme used or the request has been successfully authenticated, but the security subject is not authorized for the operation." + }, + "404": { + "description": "The collection or document specified was not found." + }, + "406": { + "description": "The requested content type (in `Accept` header) is not supported." + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + }, + "post": { + "tags": [ + "generic" + ], + "summary": "CREATE: Inserts a new document into the collection", + "description": "Using this operation you can insert new documents into collection. Normally the operation ends with 201 HTTP status code, `Last-Modified` and `Location` headers specified and with an empty response content. `identifier` can be parsed from the `Location` response header.\n\nWhen the document to post is marked as a duplicate (using rules described at `API3_DEDUP_FALLBACK_ENABLED` switch), the update operation takes place instead of inserting. In this case the original document in the collection is found and it gets updated by the actual operation POST body. Finally the operation ends with 204 HTTP status code along with `Last-Modified` and correct `Location` headers.\n\nThis operation provides autopruning of the collection (if autopruning is enabled).\n\nThis operation requires `create` (and/or `update` for deduplication) permission for the API and the collection (e.g. `api:treatments:create` and `api:treatments:update`)", + "parameters": [ + { + "name": "collection", + "in": "path", + "description": "Collection to which the operation is targeted", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "$ref": "#/components/schemas/paramCollection" + } + }, + { + "name": "Date", + "in": "header", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + { + "name": "now", + "in": "query", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "integer", + "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" + } + } + ], + "requestBody": { + "description": "JSON with new document to insert", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DocumentToPost" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Successfully created a new document in collection", + "headers": { + "Last-Modified": { + "$ref": "#/components/schemas/headerLastModified" + }, + "Location": { + "$ref": "#/components/schemas/headerLocation" + } + } + }, + "204": { + "description": "Successfully finished operation", + "headers": { + "Last-Modified": { + "$ref": "#/components/schemas/headerLastModified" + }, + "Location": { + "$ref": "#/components/schemas/headerLocation" + } + } + }, + "400": { + "description": "The request is malformed. There may be some required parameters missing or there are unrecognized parameters present." + }, + "401": { + "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy." + }, + "403": { + "description": "Insecure HTTP scheme used or the request has been successfully authenticated, but the security subject is not authorized for the operation." + }, + "404": { + "description": "The collection or document specified was not found." + }, + "422": { + "description": "The client request is well formed but a server validation error occured. Eg. when trying to modify or delete a read-only document (having `isReadOnly=true`)." + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + } + }, + "/{collection}/{identifier}": { + "get": { + "tags": [ + "generic" + ], + "summary": "READ: Retrieves a single document from the collection", + "description": "Basically this operation looks for a document matching the `identifier` field returning 200 or 404 HTTP status code.\n\nIf the document has been found in the collection but it had already been deleted, 410 HTTP status code with empty response content is to be returned.\n\nWhen `If-Modified-Since` header is used and its value is greater than the timestamp of the document in the collection, 304 HTTP status code with empty response content is returned. It means that the document has not been modified on server since the last retrieval to client side. With `If-Modified-Since` header and less or equal timestamp `srvModified` a normal 200 HTTP status with full response is returned.\n\nThis operation requires `read` permission for the API and the collection (e.g. `api:treatments:read`)", + "parameters": [ + { + "name": "collection", + "in": "path", + "description": "Collection to which the operation is targeted", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "$ref": "#/components/schemas/paramCollection" + } + }, + { + "name": "identifier", + "in": "path", + "description": "Identifier of the document to which the operation is targeted", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "$ref": "#/components/schemas/paramIdentifier" + } + }, + { + "name": "Date", + "in": "header", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + { + "name": "now", + "in": "query", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "integer", + "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": "If-Modified-Since", + "in": "header", + "description": "Timestamp (defined with respect to server's clock) of the last document modification formatted as:\n\n<day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT\n\nIf this header is present, the operation will compare its value with the srvModified timestamp of the document at first and the operation result then may differ. The srvModified timestamp was defined by server's clock.\n\nExample:\n\n
If-Modified-Since: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + { + "name": "fields", + "in": "query", + "description": "A chosen set of fields to return in response. Either you can enumerate specific fields of interest or use the predefined set. Sample parameter values:\n\n_all: All fields will be returned (default value)\n\ndate,insulin: Only fields `date` and `insulin` will be returned", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string", + "default": "_all" + }, + "examples": { + "all": { + "summary": "All fields will be returned (default behaviour)", + "value": "_all" + }, + "customSet": { + "summary": "Only fields date and insulin will be returned", + "value": "date,insulin" + } + } + } + ], + "responses": { + "200": { + "description": "The document has been succesfully found and its JSON form returned in the response content.", + "headers": { + "Last-Modified": { + "$ref": "#/components/schemas/headerLastModified" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Document" + } + }, + "text/csv": { + "schema": { + "$ref": "#/components/schemas/Document" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Document" + } + } + } + }, + "304": { + "description": "The document has not been modified on the server since timestamp specified in If-Modified-Since header", + "headers": { + "Last-Modified": { + "$ref": "#/components/schemas/headerLastModified" + } + } + }, + "401": { + "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy." + }, + "403": { + "description": "Insecure HTTP scheme used or the request has been successfully authenticated, but the security subject is not authorized for the operation." + }, + "404": { + "description": "The collection or document specified was not found." + }, + "406": { + "description": "The requested content type (in `Accept` header) is not supported." + }, + "410": { + "description": "The requested document has already been deleted." + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + }, + "put": { + "tags": [ + "generic" + ], + "summary": "UPDATE: Updates a document in the collection", + "description": "Normally the document with the matching `identifier` will be replaced in the collection by the whole JSON request body and 204 HTTP status code will be returned with empty response body.\n\nIf the document has been found in the collection but it had already been deleted, 410 HTTP status code with empty response content is to be returned.\n\nWhen no document with `identifier` has been found in the collection, then an insert operation takes place instead of updating. Finally 201 HTTP status code is returned with only `Last-Modified` header (`identifier` is already known from the path parameter).\n\nYou can also specify `If-Unmodified-Since` request header including your timestamp of document's last modification. If the document has been modified by somebody else on the server afterwards (and you do not know about it), the 412 HTTP status code is returned cancelling the update operation. You can use this feature to prevent race condition problems.\n\nThis operation provides autopruning of the collection (if autopruning is enabled).\n\nThis operation requires `update` (and/or `create`) permission for the API and the collection (e.g. `api:treatments:update` and `api:treatments:create`)", + "parameters": [ + { + "name": "collection", + "in": "path", + "description": "Collection to which the operation is targeted", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "$ref": "#/components/schemas/paramCollection" + } + }, + { + "name": "identifier", + "in": "path", + "description": "Identifier of the document to which the operation is targeted", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "$ref": "#/components/schemas/paramIdentifier" + } + }, + { + "name": "Date", + "in": "header", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + { + "name": "now", + "in": "query", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "integer", + "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": "If-Unmodified-Since", + "in": "header", + "description": "Timestamp (defined with respect to server's clock) of the last document modification formatted as:\n\n<day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT\n\nIf this header is present, the operation will compare its value with the srvModified timestamp of the document at first and the operation result then may differ. The srvModified timestamp was defined by server's clock.\n\nExample:\n\n
If-Unmodified-Since: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "JSON of new version of document (`identifier` in JSON is ignored if present)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DocumentToPost" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Successfully created a new document in collection", + "headers": { + "Last-Modified": { + "$ref": "#/components/schemas/headerLastModified" + } + } + }, + "204": { + "description": "Successfully finished operation", + "headers": { + "Last-Modified": { + "$ref": "#/components/schemas/headerLastModified" + }, + "Location": { + "$ref": "#/components/schemas/headerLocation" + } + } + }, + "400": { + "description": "The request is malformed. There may be some required parameters missing or there are unrecognized parameters present." + }, + "401": { + "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy." + }, + "403": { + "description": "Insecure HTTP scheme used or the request has been successfully authenticated, but the security subject is not authorized for the operation." + }, + "404": { + "description": "The collection or document specified was not found." + }, + "410": { + "description": "The requested document has already been deleted." + }, + "412": { + "description": "The document has already been modified on the server since specified timestamp (in If-Unmodified-Since header)." + }, + "422": { + "description": "The client request is well formed but a server validation error occured. Eg. when trying to modify or delete a read-only document (having `isReadOnly=true`)." + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + }, + "delete": { + "tags": [ + "generic" + ], + "summary": "DELETE: Deletes a document from the collection", + "description": "If the document has already been deleted, the operation will succeed anyway. Normally, documents are not really deleted from the collection but they are only marked as deleted. For special cases the deletion can be irreversible using `permanent` parameter.\n\nThis operation provides autopruning of the collection (if autopruning is enabled).\n\nThis operation requires `delete` permission for the API and the collection (e.g. `api:treatments:delete`)", + "parameters": [ + { + "name": "collection", + "in": "path", + "description": "Collection to which the operation is targeted", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "$ref": "#/components/schemas/paramCollection" + } + }, + { + "name": "identifier", + "in": "path", + "description": "Identifier of the document to which the operation is targeted", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "$ref": "#/components/schemas/paramIdentifier" + } + }, + { + "name": "Date", + "in": "header", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + { + "name": "now", + "in": "query", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "integer", + "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": "permanent", + "in": "query", + "description": "If true, the deletion will be irreversible and it will not appear in `HISTORY` operation. Normally there is no reason for setting this flag.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "204": { + "description": "Successful operation - empty response" + }, + "401": { + "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy." + }, + "403": { + "description": "Insecure HTTP scheme used or the request has been successfully authenticated, but the security subject is not authorized for the operation." + }, + "404": { + "description": "The collection or document specified was not found." + }, + "422": { + "description": "The client request is well formed but a server validation error occured. Eg. when trying to modify or delete a read-only document (having `isReadOnly=true`)." + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + }, + "patch": { + "tags": [ + "generic" + ], + "summary": "PATCH: Partially updates document in the collection", + "description": "Normally the document with the matching `identifier` will be retrieved from the collection and it will be patched by all specified fields from the JSON request body. Finally 204 HTTP status code will be returned with empty response body.\n\nIf the document has been found in the collection but it had already been deleted, 410 HTTP status code with empty response content is to be returned.\n\nWhen no document with `identifier` has been found in the collection, then the operation ends with 404 HTTP status code.\n\nYou can also specify `If-Unmodified-Since` request header including your timestamp of document's last modification. If the document has been modified by somebody else on the server afterwards (and you do not know about it), the 412 HTTP status code is returned cancelling the update operation. You can use this feature to prevent race condition problems.\n\n`PATCH` operation can save some bandwidth for incremental document updates in comparison with `GET` - `UPDATE` operation sequence.\n\nWhile patching the document, the field `modifiedBy` is automatically set to the authorized subject's name.\n\nThis operation provides autopruning of the collection (if autopruning is enabled).\n\nThis operation requires `update` permission for the API and the collection (e.g. `api:treatments:update`)", + "parameters": [ + { + "name": "collection", + "in": "path", + "description": "Collection to which the operation is targeted", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "$ref": "#/components/schemas/paramCollection" + } + }, + { + "name": "identifier", + "in": "path", + "description": "Identifier of the document to which the operation is targeted", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "$ref": "#/components/schemas/paramIdentifier" + } + }, + { + "name": "Date", + "in": "header", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + { + "name": "now", + "in": "query", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "integer", + "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": "If-Unmodified-Since", + "in": "header", + "description": "Timestamp (defined with respect to server's clock) of the last document modification formatted as:\n\n<day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT\n\nIf this header is present, the operation will compare its value with the srvModified timestamp of the document at first and the operation result then may differ. The srvModified timestamp was defined by server's clock.\n\nExample:\n\n
If-Unmodified-Since: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "JSON of new version of document (`identifier` in JSON is ignored if present)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DocumentToPost" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Successfully finished operation", + "headers": { + "Last-Modified": { + "$ref": "#/components/schemas/headerLastModified" + }, + "Location": { + "$ref": "#/components/schemas/headerLocation" + } + } + }, + "400": { + "description": "The request is malformed. There may be some required parameters missing or there are unrecognized parameters present." + }, + "401": { + "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy." + }, + "403": { + "description": "Insecure HTTP scheme used or the request has been successfully authenticated, but the security subject is not authorized for the operation." + }, + "404": { + "description": "The collection or document specified was not found." + }, + "410": { + "description": "The requested document has already been deleted." + }, + "412": { + "description": "The document has already been modified on the server since specified timestamp (in If-Unmodified-Since header)." + }, + "422": { + "description": "The client request is well formed but a server validation error occured. Eg. when trying to modify or delete a read-only document (having `isReadOnly=true`)." + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + } + }, + "/{collection}/history": { + "get": { + "tags": [ + "generic" + ], + "summary": "HISTORY: Retrieves incremental changes since timestamp", + "description": "HISTORY operation is intended for continuous data synchronization with other systems.\nEvery insertion, update and deletion will be included in the resulting JSON array of documents (since timestamp in `Last-Modified` request header value). All changes are listed chronologically in response with 200 HTTP status code. The maximum listed `srvModified` timestamp is also stored in `Last-Modified` and `ETag` response headers that you can use for future, directly following synchronization. You can also limit the array's length using `limit` parameter.\n\nDeleted documents will appear with `isValid` = `false` field.\n\nWhen there is no change detected since the timestamp the operation ends with 204 HTTP status code and empty response content.\n\nHISTORY operation has a fallback mechanism in place for documents, which were not created by API v3. For such documents `srvModified` is virtually assigned from the `date` field (for `entries` collection) or from the `created_at` field (for other collections).\n\nThis operation requires `read` permission for the API and the collection (e.g. `api:treatments:read`)\n\nThe only exception is the `settings` collection which requires `admin` permission (`api:settings:admin`), because the settings of each application should be isolated and kept secret. You need to know the concrete identifier to access the app's settings.", + "operationId": "HISTORY", + "parameters": [ + { + "name": "collection", + "in": "path", + "description": "Collection to which the operation is targeted", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "$ref": "#/components/schemas/paramCollection" + } + }, + { + "name": "Date", + "in": "header", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + { + "name": "now", + "in": "query", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "integer", + "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": "Last-Modified", + "in": "header", + "description": "Starting timestamp (defined with respect to server's clock) since which the changes in documents are to be listed, formatted as:\n\n<day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT\n\nExample:\n\n
Last-Modified: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of documents to get in result array", + "required": false, + "style": "form", + "explode": true, + "schema": { + "minimum": 1, + "type": "integer", + "example": 100 + } + }, + { + "name": "fields", + "in": "query", + "description": "A chosen set of fields to return in response. Either you can enumerate specific fields of interest or use the predefined set. Sample parameter values:\n\n_all: All fields will be returned (default value)\n\ndate,insulin: Only fields `date` and `insulin` will be returned", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string", + "default": "_all" + }, + "examples": { + "all": { + "summary": "All fields will be returned (default behaviour)", + "value": "_all" + }, + "customSet": { + "summary": "Only fields date and insulin will be returned", + "value": "date,insulin" + } + } + } + ], + "responses": { + "200": { + "description": "Changed documents since specified timestamp", + "headers": { + "Last-Modified": { + "$ref": "#/components/schemas/headerLastModifiedMaximum" + }, + "ETag": { + "$ref": "#/components/schemas/headerEtagLastModifiedMaximum" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DocumentArray" + } + }, + "text/csv": { + "schema": { + "$ref": "#/components/schemas/DocumentArray" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/DocumentArray" + } + } + } + }, + "204": { + "description": "No changes detected" + }, + "400": { + "description": "The request is malformed. There may be some required parameters missing or there are unrecognized parameters present." + }, + "401": { + "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy." + }, + "403": { + "description": "Insecure HTTP scheme used or the request has been successfully authenticated, but the security subject is not authorized for the operation." + }, + "404": { + "description": "The collection or document specified was not found." + }, + "406": { + "description": "The requested content type (in `Accept` header) is not supported." + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + } + }, + "/{collection}/history/{lastModified}": { + "get": { + "tags": [ + "generic" + ], + "summary": "HISTORY: Retrieves incremental changes since timestamp", + "description": "This HISTORY operation variant is more precise than the previous one with `Last-Modified` request HTTP header), because it does not loose milliseconds precision.\n\nSince this variant queries for changed documents by timestamp precisely and exclusively, the last modified document does not repeat itself in following calls. That is the reason why is this variant more suitable for continuous synchronization with other systems.\n\nThis variant behaves quite the same as the previous one in all other aspects.", + "operationId": "HISTORY2", + "parameters": [ + { + "name": "collection", + "in": "path", + "description": "Collection to which the operation is targeted", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "$ref": "#/components/schemas/paramCollection" + } + }, + { + "name": "lastModified", + "in": "path", + "description": "Starting timestamp (in UNIX epoch format, defined with respect to server's clock) since which the changes in documents are to be listed. Query for modified documents is made using \"greater than\" operator (not including equal timestamps).", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "Date", + "in": "header", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + { + "name": "now", + "in": "query", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "integer", + "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", + "description": "Maximum number of documents to get in result array", + "required": false, + "style": "form", + "explode": true, + "schema": { + "minimum": 1, + "type": "integer", + "example": 100 + } + }, + { + "name": "fields", + "in": "query", + "description": "A chosen set of fields to return in response. Either you can enumerate specific fields of interest or use the predefined set. Sample parameter values:\n\n_all: All fields will be returned (default value)\n\ndate,insulin: Only fields `date` and `insulin` will be returned", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string", + "default": "_all" + }, + "examples": { + "all": { + "summary": "All fields will be returned (default behaviour)", + "value": "_all" + }, + "customSet": { + "summary": "Only fields date and insulin will be returned", + "value": "date,insulin" + } + } + } + ], + "responses": { + "200": { + "description": "Changed documents since specified timestamp", + "headers": { + "Last-Modified": { + "$ref": "#/components/schemas/headerLastModifiedMaximum" + }, + "ETag": { + "$ref": "#/components/schemas/headerEtagLastModifiedMaximum" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DocumentArray" + } + }, + "text/csv": { + "schema": { + "$ref": "#/components/schemas/DocumentArray" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/DocumentArray" + } + } + } + }, + "204": { + "description": "No changes detected" + }, + "400": { + "description": "The request is malformed. There may be some required parameters missing or there are unrecognized parameters present." + }, + "401": { + "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy." + }, + "403": { + "description": "Insecure HTTP scheme used or the request has been successfully authenticated, but the security subject is not authorized for the operation." + }, + "404": { + "description": "The collection or document specified was not found." + }, + "406": { + "description": "The requested content type (in `Accept` header) is not supported." + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + } + }, + "/version": { + "get": { + "tags": [ + "other" + ], + "summary": "VERSION: Returns actual version information", + "description": "No authentication is needed for this commnad (it is public)", + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Version" + } + } + } + } + } + } + }, + "/status": { + "get": { + "tags": [ + "other" + ], + "summary": "STATUS: Returns actual version information and all permissions granted for API", + "description": "This operation requires authorization in contrast with VERSION operation.", + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + }, + "401": { + "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy." + }, + "403": { + "description": "Insecure HTTP scheme used or the request has been successfully authenticated, but the security subject is not authorized for the operation." + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + } + }, + "/lastModified": { + "get": { + "tags": [ + "other" + ], + "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": "Date", + "in": "header", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + { + "name": "now", + "in": "query", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "integer", + "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" + } + } + ], + "responses": { + "200": { + "description": "Successful operation returning the timestamps", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LastModifiedResult" + } + } + } + }, + "401": { + "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy." + }, + "403": { + "description": "Insecure HTTP scheme used or the request has been successfully authenticated, but the security subject is not authorized for the operation." + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + } + } + }, + "components": { + "schemas": { + "headerLocation": { + "type": "string", + "description": "Location of document - the relative part of URL. This can be used to parse the identifier of just created document.\nExample=/api/v3/treatments/53409478-105f-11e9-ab14-d663bd873d93" + }, + "headerLastModified": { + "type": "string", + "description": "Timestamp of the last document modification on the server, formatted as\n', :: GMT'.\nThis field is relevant only for documents which were somehow modified by API v3 (inserted, updated or deleted) and it was generated using server's clock.\nExample='Wed, 17 Oct 2018 05:13:00 GMT'" + }, + "headerLastModifiedMaximum": { + "type": "string", + "description": "The latest (maximum) `srvModified` field of all returning documents, formatted as\n', :: GMT'.\nExample='Wed, 17 Oct 2018 05:13:00 GMT'" + }, + "headerEtagLastModifiedMaximum": { + "type": "string", + "description": "The latest (maximum) `srvModified` field of all returning documents. This header does not loose milliseconds from the date (unlike the `Last-Modified` header).\nExample='W/\"1525383610088\"'" + }, + "paramCollection": { + "type": "string", + "example": "treatments", + "enum": [ + "devicestatus", + "entries", + "food", + "profile", + "settings", + "treatments" + ] + }, + "paramIdentifier": { + "type": "string", + "example": "53409478-105f-11e9-ab14-d663bd873d93" + }, + "DocumentBase": { + "required": [ + "app", + "date" + ], + "properties": { + "identifier": { + "type": "string", + "description": "Main addressing, required field that identifies document in the collection.\n\nThe client should not create the identifier, the server automatically assigns it when the document is inserted.\n\nThe server calculates the identifier in such a way that duplicate records are automatically merged (deduplicating is made by `date`, `device` and `eventType` fields).\n\nThe best practise for all applications is not to loose identifiers from received documents, but save them carefully for other consumer applications/systems.\n\nAPI v3 has a fallback mechanism in place, for documents without `identifier` field the `identifier` is set to internal `_id`, when reading or addressing these documents.\n\nNote: this field is immutable by the client (it cannot be updated or patched)", + "example": "53409478-105f-11e9-ab14-d663bd873d93" + }, + "date": { + "type": "integer", + "description": "Required timestamp when the record or event occured, you can choose from three input formats\n- Unix epoch in milliseconds (1525383610088)\n- Unix epoch in seconds (1525383610)\n- ISO 8601 with optional timezone ('2018-05-03T21:40:10.088Z' or '2018-05-03T23:40:10.088+02:00')\n\nThe date is always stored in a normalized form - UTC with zero offset. If UTC offset was present, it is going to be set in the `utcOffset` field.\n\nNote: this field is immutable by the client (it cannot be updated or patched)", + "format": "int64", + "example": 1525383610088 + }, + "utcOffset": { + "type": "integer", + "description": "Local UTC offset (timezone) of the event in minutes. This field can be set either directly by the client (in the incoming document) or it is automatically parsed from the `date` field.\n\nNote: this field is immutable by the client (it cannot be updated or patched)", + "example": 120 + }, + "app": { + "type": "string", + "description": "Application or system in which the record was entered by human or device for the first time.\n\nNote: this field is immutable by the client (it cannot be updated or patched)", + "example": "xdrip" + }, + "device": { + "type": "string", + "description": "The device from which the data originated (including serial number of the device, if it is relevant and safe).\n\nNote: this field is immutable by the client (it cannot be updated or patched)", + "example": "dexcom G5" + }, + "_id": { + "type": "string", + "description": "Internally assigned database id. This field is for internal server purposes only, clients communicate with API by using identifier field.", + "example": "58e9dfbc166d88cc18683aac" + }, + "srvCreated": { + "type": "integer", + "description": "The server's timestamp of document insertion into the database (Unix epoch in ms). This field appears only for documents which were inserted by API v3.\n\nNote: this field is immutable by the client (it cannot be updated or patched)", + "format": "int64", + "example": 1525383610088 + }, + "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)", + "example": "uploader" + }, + "srvModified": { + "type": "integer", + "description": "The server's timestamp of the last document modification in the database (Unix epoch in ms). This field appears only for documents which were somehow modified by API v3 (inserted, updated or deleted).\n\nNote: this field is immutable by the client (it cannot be updated or patched)", + "format": "int64", + "example": 1525383610088 + }, + "modifiedBy": { + "type": "string", + "description": "Name of the security subject (within Nightscout scope) which has patched or deleted the document for the last time. This field is automatically set by the server.\n\nNote: this field is immutable by the client (it cannot be updated or patched)", + "example": "admin" + }, + "isValid": { + "type": "boolean", + "description": "A flag set by the server only for deleted documents. This field appears only within history operation and for documents which were deleted by API v3 (and they always have a false value)\n\nNote: this field is immutable by the client (it cannot be updated or patched)", + "example": false + }, + "isReadOnly": { + "type": "boolean", + "description": "A flag set by client that locks the document from any changes. Every document marked with `isReadOnly=true` is forever immutable and cannot even be deleted.\n\nAny attempt to modify the read-only document will end with status 422 UNPROCESSABLE ENTITY.", + "example": true + } + }, + "description": "Shared base for all documents" + }, + "DeviceStatus": { + "description": "State of physical device, which is a technical part of the whole T1D compensation system", + "allOf": [ + { + "$ref": "#/components/schemas/DocumentBase" + }, + { + "type": "object", + "properties": { + "some_property": { + "type": "string", + "description": "..." + } + } + } + ] + }, + "Entry": { + "description": "Blood glucose measurements and CGM calibrations", + "allOf": [ + { + "$ref": "#/components/schemas/DocumentBase" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "sgv, mbg, cal, etc" + }, + "sgv": { + "type": "number", + "description": "The glucose reading. (only available for sgv types)" + }, + "direction": { + "type": "string", + "description": "Direction of glucose trend reported by CGM. (only available for sgv types)", + "example": "\"DoubleDown\", \"SingleDown\", \"FortyFiveDown\", \"Flat\", \"FortyFiveUp\", \"SingleUp\", \"DoubleUp\", \"NOT COMPUTABLE\", \"RATE OUT OF RANGE\" for xdrip" + }, + "noise": { + "type": "number", + "description": "Noise level at time of reading. (only available for sgv types)" + }, + "filtered": { + "type": "number", + "description": "The raw filtered value directly from CGM transmitter. (only available for sgv types)" + }, + "unfiltered": { + "type": "number", + "description": "The raw unfiltered value directly from CGM transmitter. (only available for sgv types)" + }, + "rssi": { + "type": "number", + "description": "The signal strength from CGM transmitter. (only available for sgv types)" + }, + "units": { + "type": "string", + "description": "The units for the glucose value, mg/dl or mmol/l. It is strongly recommended to fill in this field.", + "example": "\"mg\", \"mmol\"" + } + } + } + ] + }, + "Food": { + "description": "Nutritional values of food", + "allOf": [ + { + "$ref": "#/components/schemas/DocumentBase" + }, + { + "type": "object", + "properties": { + "food": { + "type": "string", + "description": "food, quickpick" + }, + "category": { + "type": "string", + "description": "Name for a group of related records" + }, + "subcategory": { + "type": "string", + "description": "Name for a second level of groupping" + }, + "name": { + "type": "string", + "description": "Name of the food described" + }, + "portion": { + "type": "number", + "description": "Number of units (e.g. grams) of the whole portion described" + }, + "unit": { + "type": "string", + "description": "Unit for the portion", + "example": "\"g\", \"ml\", \"oz\"" + }, + "carbs": { + "type": "number", + "description": "Amount of carbs in the portion in grams" + }, + "fat": { + "type": "number", + "description": "Amount of fat in the portion in grams" + }, + "protein": { + "type": "number", + "description": "Amount of proteins in the portion in grams" + }, + "energy": { + "type": "number", + "description": "Amount of energy in the portion in kJ" + }, + "gi": { + "type": "number", + "description": "Glycemic index (1=low, 2=medium, 3=high)" + }, + "hideafteruse": { + "type": "boolean", + "description": "Flag used for quickpick" + }, + "hidden": { + "type": "boolean", + "description": "Flag used for quickpick" + }, + "position": { + "type": "number", + "description": "Ordering field for quickpick" + }, + "portions": { + "type": "number", + "description": "component multiplier if defined inside quickpick compound" + }, + "foods": { + "type": "array", + "description": "Neighbour documents (from food collection) that together make a quickpick compound", + "items": { + "$ref": "#/components/schemas/Food" + } + } + } + } + ] + }, + "Profile": { + "description": "Parameters describing body functioning relative to T1D + compensation parameters", + "allOf": [ + { + "$ref": "#/components/schemas/DocumentBase" + }, + { + "type": "object", + "properties": { + "some_property": { + "type": "string", + "description": "..." + } + } + } + ] + }, + "Settings": { + "description": "A document representing persisted settings of some application or system (it could by Nightscout itself as well). This pack of options serves as a backup or as a shared centralized storage for multiple client instances. It is a probably good idea to `PATCH` the document instead of `UPDATE` operation, e.g. when changing one settings option in a client application.\n\n`identifier` represents a client application name here, e.g. `xdrip` or `aaps`.\n\n`Settings` collection has a more specific authorization required. For the `SEARCH` operation within this collection, you need an `admin` permission, such as `api:settings:admin`. The goal is to isolate individual client application settings.", + "allOf": [ + { + "$ref": "#/components/schemas/DocumentBase" + }, + { + "type": "object", + "properties": { + "some_property": { + "type": "string", + "description": "..." + } + } + } + ] + }, + "Treatment": { + "description": "T1D compensation action", + "allOf": [ + { + "$ref": "#/components/schemas/DocumentBase" + }, + { + "type": "object", + "properties": { + "eventType": { + "type": "string", + "description": "The type of treatment event.\n\nNote: this field is immutable by the client (it cannot be updated or patched)", + "example": "\"BG Check\", \"Snack Bolus\", \"Meal Bolus\", \"Correction Bolus\", \"Carb Correction\", \"Combo Bolus\", \"Announcement\", \"Note\", \"Question\", \"Exercise\", \"Site Change\", \"Sensor Start\", \"Sensor Change\", \"Pump Battery Change\", \"Insulin Change\", \"Temp Basal\", \"Profile Switch\", \"D.A.D. Alert\", \"Temporary Target\", \"OpenAPS Offline\", \"Bolus Wizard\"" + }, + "glucose": { + "type": "string", + "description": "Current glucose." + }, + "glucoseType": { + "type": "string", + "description": "Method used to obtain glucose, Finger or Sensor.", + "example": "\"Sensor\", \"Finger\", \"Manual\"" + }, + "units": { + "type": "string", + "description": "The units for the glucose value, mg/dl or mmol/l. It is strongly recommended to fill in this field when `glucose` is entered.", + "example": "\"mg/dl\", \"mmol/l\"" + }, + "carbs": { + "type": "number", + "description": "Amount of carbs given." + }, + "protein": { + "type": "number", + "description": "Amount of protein given." + }, + "fat": { + "type": "number", + "description": "Amount of fat given." + }, + "insulin": { + "type": "number", + "description": "Amount of insulin, if any." + }, + "duration": { + "type": "number", + "description": "Duration in minutes." + }, + "preBolus": { + "type": "number", + "description": "How many minutes the bolus was given before the meal started." + }, + "splitNow": { + "type": "number", + "description": "Immediate part of combo bolus (in percent)." + }, + "splitExt": { + "type": "number", + "description": "Extended part of combo bolus (in percent)." + }, + "percent": { + "type": "number", + "description": "Eventual basal change in percent." + }, + "absolute": { + "type": "number", + "description": "Eventual basal change in absolute value (insulin units per hour)." + }, + "targetTop": { + "type": "number", + "description": "Top limit of temporary target." + }, + "targetBottom": { + "type": "number", + "description": "Bottom limit of temporary target." + }, + "profile": { + "type": "string", + "description": "Name of the profile to which the pump has been switched." + }, + "reason": { + "type": "string", + "description": "For example the reason why the profile has been switched or why the temporary target has been set." + }, + "notes": { + "type": "string", + "description": "Description/notes of treatment." + }, + "enteredBy": { + "type": "string", + "description": "Who entered the treatment." + } + } + } + ] + }, + "DocumentToPost": { + "type": "object", + "description": "Single document", + "example": { + "identifier": "53409478-105f-11e9-ab14-d663bd873d93", + "date": 1532936118000, + "utcOffset": 120, + "carbs": 10, + "insulin": 1, + "eventType": "Snack Bolus", + "app": "xdrip", + "subject": "uploader" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/DeviceStatus" + }, + { + "$ref": "#/components/schemas/Entry" + }, + { + "$ref": "#/components/schemas/Food" + }, + { + "$ref": "#/components/schemas/Profile" + }, + { + "$ref": "#/components/schemas/Settings" + }, + { + "$ref": "#/components/schemas/Treatment" + } + ] + }, + "Document": { + "type": "object", + "description": "Single document", + "example": { + "identifier": "53409478-105f-11e9-ab14-d663bd873d93", + "date": 1532936118000, + "utcOffset": 120, + "carbs": 10, + "insulin": 1, + "eventType": "Snack Bolus", + "srvCreated": 1532936218000, + "srvModified": 1532936218000, + "app": "xdrip", + "subject": "uploader", + "modifiedBy": "admin" + }, + "xml": { + "name": "item" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/DeviceStatus" + }, + { + "$ref": "#/components/schemas/Entry" + }, + { + "$ref": "#/components/schemas/Food" + }, + { + "$ref": "#/components/schemas/Profile" + }, + { + "$ref": "#/components/schemas/Settings" + }, + { + "$ref": "#/components/schemas/Treatment" + } + ] + }, + "DeviceStatusArray": { + "type": "array", + "description": "Array of documents", + "items": { + "$ref": "#/components/schemas/DeviceStatus" + } + }, + "EntryArray": { + "type": "array", + "description": "Array of documents", + "items": { + "$ref": "#/components/schemas/Entry" + } + }, + "FoodArray": { + "type": "array", + "description": "Array of documents", + "items": { + "$ref": "#/components/schemas/Food" + } + }, + "ProfileArray": { + "type": "array", + "description": "Array of documents", + "items": { + "$ref": "#/components/schemas/Profile" + } + }, + "SettingsArray": { + "type": "array", + "description": "Array of settings", + "items": { + "$ref": "#/components/schemas/Settings" + } + }, + "TreatmentArray": { + "type": "array", + "description": "Array of documents", + "items": { + "$ref": "#/components/schemas/Treatment" + } + }, + "DocumentArray": { + "type": "object", + "xml": { + "name": "items" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/DeviceStatusArray" + }, + { + "$ref": "#/components/schemas/EntryArray" + }, + { + "$ref": "#/components/schemas/FoodArray" + }, + { + "$ref": "#/components/schemas/ProfileArray" + }, + { + "$ref": "#/components/schemas/SettingsArray" + }, + { + "$ref": "#/components/schemas/TreatmentArray" + } + ] + }, + "Version": { + "type": "object", + "properties": { + "version": { + "type": "string", + "description": "The whole Nightscout instance version", + "example": "0.10.2-release-20171201" + }, + "apiVersion": { + "type": "string", + "description": "API v3 subsystem version", + "example": "3.0.0" + }, + "srvDate": { + "type": "number", + "description": "Actual server date and time in UNIX epoch format", + "example": 1532936118000 + }, + "storage": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Type of storage engine used", + "example": "mongodb" + }, + "version": { + "type": "string", + "description": "Version of the storage engine", + "example": "4.0.6" + } + } + } + }, + "description": "Information about versions" + }, + "Status": { + "description": "Information about versions and API permissions", + "allOf": [ + { + "$ref": "#/components/schemas/Version" + }, + { + "type": "object", + "properties": { + "apiPermissions": { + "type": "object", + "properties": { + "devicestatus": { + "type": "string", + "example": "crud" + }, + "entries": { + "type": "string", + "example": "r" + }, + "food": { + "type": "string", + "example": "crud" + }, + "profile": { + "type": "string", + "example": "r" + }, + "treatments": { + "type": "string", + "example": "crud" + } + } + } + } + } + ] + }, + "LastModifiedResult": { + "properties": { + "srvDate": { + "type": "integer", + "description": "Actual storage server date (Unix epoch in ms).", + "format": "int64", + "example": 1556260878776 + }, + "collections": { + "type": "object", + "properties": { + "devicestatus": { + "type": "integer", + "description": "Timestamp of the last modification (Unix epoch in ms), `null` when there is no timestamp found.", + "format": "int64", + "example": 1556260760974 + }, + "treatments": { + "type": "integer", + "description": "Timestamp of the last modification (Unix epoch in ms), `null` when there is no timestamp found.", + "format": "int64", + "example": 1553374184169 + }, + "entries": { + "type": "integer", + "description": "Timestamp of the last modification (Unix epoch in ms), `null` when there is no timestamp found.", + "format": "int64", + "example": 1556260758768 + }, + "profile": { + "type": "integer", + "description": "Timestamp of the last modification (Unix epoch in ms), `null` when there is no timestamp found.", + "format": "int64", + "example": 1548524042744 + } + }, + "description": "Collections which the user have read access to." + } + }, + "description": "Result of LAST MODIFIED operation" + } + }, + "responses": { + "201Created": { + "description": "Successfully created a new document in collection", + "headers": { + "Last-Modified": { + "$ref": "#/components/schemas/headerLastModified" + } + } + }, + "201CreatedLocation": { + "description": "Successfully created a new document in collection", + "headers": { + "Last-Modified": { + "$ref": "#/components/schemas/headerLastModified" + }, + "Location": { + "$ref": "#/components/schemas/headerLocation" + } + } + }, + "204NoContent": { + "description": "Successfully finished operation", + "headers": { + "Last-Modified": { + "$ref": "#/components/schemas/headerLastModified" + } + } + }, + "204NoContentLocation": { + "description": "Successfully finished operation", + "headers": { + "Last-Modified": { + "$ref": "#/components/schemas/headerLastModified" + }, + "Location": { + "$ref": "#/components/schemas/headerLocation" + } + } + }, + "304NotModified": { + "description": "The document has not been modified on the server since timestamp specified in If-Modified-Since header", + "headers": { + "Last-Modified": { + "$ref": "#/components/schemas/headerLastModified" + } + } + }, + "400BadRequest": { + "description": "The request is malformed. There may be some required parameters missing or there are unrecognized parameters present." + }, + "401Unauthorized": { + "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy." + }, + "403Forbidden": { + "description": "Insecure HTTP scheme used or the request has been successfully authenticated, but the security subject is not authorized for the operation." + }, + "404NotFound": { + "description": "The collection or document specified was not found." + }, + "406NotAcceptable": { + "description": "The requested content type (in `Accept` header) is not supported." + }, + "412PreconditionFailed": { + "description": "The document has already been modified on the server since specified timestamp (in If-Unmodified-Since header)." + }, + "410Gone": { + "description": "The requested document has already been deleted." + }, + "422UnprocessableEntity": { + "description": "The client request is well formed but a server validation error occured. Eg. when trying to modify or delete a read-only document (having `isReadOnly=true`)." + }, + "search200": { + "description": "Successful operation returning array of documents matching the filtering criteria", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DocumentArray" + } + }, + "text/csv": { + "schema": { + "$ref": "#/components/schemas/DocumentArray" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/DocumentArray" + } + } + } + }, + "search204": { + "description": "Successful operation - no documents matching the filtering criteria" + }, + "read200": { + "description": "The document has been succesfully found and its JSON form returned in the response content.", + "headers": { + "Last-Modified": { + "$ref": "#/components/schemas/headerLastModified" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Document" + } + }, + "text/csv": { + "schema": { + "$ref": "#/components/schemas/Document" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Document" + } + } + } + }, + "history200": { + "description": "Changed documents since specified timestamp", + "headers": { + "Last-Modified": { + "$ref": "#/components/schemas/headerLastModifiedMaximum" + }, + "ETag": { + "$ref": "#/components/schemas/headerEtagLastModifiedMaximum" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DocumentArray" + } + }, + "text/csv": { + "schema": { + "$ref": "#/components/schemas/DocumentArray" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/DocumentArray" + } + } + } + }, + "history204": { + "description": "No changes detected" + }, + "lastModified200": { + "description": "Successful operation returning the timestamps", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LastModifiedResult" + } + } + } + } + }, + "parameters": { + "dateHeader": { + "name": "Date", + "in": "header", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + "nowParam": { + "name": "now", + "in": "query", + "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + "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", + "description": "Maximum number of documents to get in result array", + "required": false, + "style": "form", + "explode": true, + "schema": { + "minimum": 1, + "type": "integer", + "example": 100 + } + }, + "skipParam": { + "name": "skip", + "in": "query", + "description": "Number of documents to skip from collection query before loading them into result array (used for pagination)", + "required": false, + "style": "form", + "explode": true, + "schema": { + "minimum": 0, + "type": "integer", + "example": 0, + "default": 0 + } + }, + "sortParam": { + "name": "sort", + "in": "query", + "description": "Field name by which the sorting of documents is performed. This parameter cannot be combined with `sort$desc` parameter.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string" + } + }, + "sortDescParam": { + "name": "sort$desc", + "in": "query", + "description": "Field name by which the descending (reverse) sorting of documents is performed. This parameter cannot be combined with `sort` parameter.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string" + } + }, + "permanentParam": { + "name": "permanent", + "in": "query", + "description": "If true, the deletion will be irreversible and it will not appear in `HISTORY` operation. Normally there is no reason for setting this flag.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "boolean" + } + }, + "fieldsParam": { + "name": "fields", + "in": "query", + "description": "A chosen set of fields to return in response. Either you can enumerate specific fields of interest or use the predefined set. Sample parameter values:\n\n_all: All fields will be returned (default value)\n\ndate,insulin: Only fields `date` and `insulin` will be returned", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string", + "default": "_all" + }, + "examples": { + "all": { + "summary": "All fields will be returned (default behaviour)", + "value": "_all" + }, + "customSet": { + "summary": "Only fields date and insulin will be returned", + "value": "date,insulin" + } + } + }, + "filterParams": { + "name": "filter_parameters", + "in": "query", + "description": "Any number of filtering operators.\n\nEach filtering operator has name like `$`, e.g. `carbs$gt=2` which represents filtering rule \"The field carbs must be present and greater than 2\".\n\nYou can choose from operators:\n\n`eq`=equals, `insulin$eq=1.5`\n\n`ne`=not equals, `insulin$ne=1.5`\n\n`gt`=greater than, `carbs$gt=30`\n\n`gte`=greater than or equal, `carbs$gte=30`\n\n`lt`=less than, `carbs$lt=30`\n\n`lte`=less than or equal, `carbs$lte=30`\n\n`in`=in specified set, `type$in=sgv|mbg|cal`\n\n`nin`=not in specified set, `eventType$nin=Temp%20Basal|Temporary%20Target`\n\n`re`=regex pattern, `eventType$re=Temp.%2A`\n\nWhen filtering by field `date`, `created_at`, `srvModified` or `srvCreated`, you can choose from three input formats\n- Unix epoch in milliseconds (1525383610088)\n- Unix epoch in seconds (1525383610)\n- ISO 8601 with optional timezone ('2018-05-03T21:40:10.088Z' or '2018-05-03T23:40:10.088+02:00')\n\nThe date is always queried in a normalized form - UTC with zero offset and with the correct format (1525383610088 for `date`, '2018-05-03T21:40:10.088Z' for `created_at`).", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string" + } + }, + "lastModifiedRequiredHeader": { + "name": "Last-Modified", + "in": "header", + "description": "Starting timestamp (defined with respect to server's clock) since which the changes in documents are to be listed, formatted as:\n\n<day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT\n\nExample:\n\n
Last-Modified: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + "ifModifiedSinceHeader": { + "name": "If-Modified-Since", + "in": "header", + "description": "Timestamp (defined with respect to server's clock) of the last document modification formatted as:\n\n<day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT\n\nIf this header is present, the operation will compare its value with the srvModified timestamp of the document at first and the operation result then may differ. The srvModified timestamp was defined by server's clock.\n\nExample:\n\n
If-Modified-Since: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + "ifUnmodifiedSinceHeader": { + "name": "If-Unmodified-Since", + "in": "header", + "description": "Timestamp (defined with respect to server's clock) of the last document modification formatted as:\n\n<day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT\n\nIf this header is present, the operation will compare its value with the srvModified timestamp of the document at first and the operation result then may differ. The srvModified timestamp was defined by server's clock.\n\nExample:\n\n
If-Unmodified-Since: Wed, 17 Oct 2018 05:13:00 GMT
", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + } + }, + "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.", + "scheme": "bearer", + "bearerFormat": "JWT" + } + } + } +} diff --git a/tests/api3.basic.test.js b/tests/api3.basic.test.js index fc7a885269f..d13b8628562 100644 --- a/tests/api3.basic.test.js +++ b/tests/api3.basic.test.js @@ -23,15 +23,6 @@ describe('Basic REST API3', function() { }); - it('GET /swagger', async () => { - let res = await request(self.app) - .get('/api/v3/swagger.yaml') - .expect(200); - - res.header['content-length'].should.be.above(0); - }); - - it('GET /version', async () => { let res = await request(self.app) .get('/api/v3/version') From e05f99c76713b337c3aa8a6a9d866bb6c384e415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Ondr=C5=AF=C5=A1ek?= Date: Tue, 9 Feb 2021 14:33:57 +0100 Subject: [PATCH 4/4] APIv3: finishing cache invalidation tests --- lib/api3/const.json | 6 +- lib/api3/doc/security.md | 16 -- lib/api3/doc/tutorial.md | 18 +-- lib/api3/index.js | 1 - lib/api3/security.js | 45 +----- lib/api3/swagger.json | 256 ++------------------------------ lib/api3/swagger.yaml | 54 +------ tests/api3.security.test.js | 90 +---------- tests/fixtures/api3/instance.js | 10 +- 9 files changed, 33 insertions(+), 463 deletions(-) diff --git a/lib/api3/const.json b/lib/api3/const.json index e82104ddb53..5f63c63679a 100644 --- a/lib/api3/const.json +++ b/lib/api3/const.json @@ -1,7 +1,6 @@ { - "API3_VERSION": "3.0.2-alpha", + "API3_VERSION": "3.0.3-alpha", "API3_SECURITY_ENABLE": true, - "API3_TIME_SKEW_TOLERANCE": 5, "API3_DEDUP_FALLBACK_ENABLED": true, "API3_CREATED_AT_FALLBACK_ENABLED": true, "API3_MAX_LIMIT": 1000, @@ -34,10 +33,7 @@ "HTTP_400_SORT_SORT_DESC": "Parameters sort and sort_desc cannot be combined", "HTTP_400_UNSUPPORTED_FILTER_OPERATOR": "Unsupported filter operator {0}", "HTTP_400_IMMUTABLE_FIELD": "Field {0} cannot be modified by the client", - "HTTP_401_BAD_DATE": "Bad Date header", "HTTP_401_BAD_TOKEN": "Bad access token or JWT", - "HTTP_401_DATE_OUT_OF_TOLERANCE": "Date header out of tolerance", - "HTTP_401_MISSING_DATE": "Missing Date header", "HTTP_401_MISSING_OR_BAD_TOKEN": "Missing or bad access token or JWT", "HTTP_403_MISSING_PERMISSION": "Missing permission {0}", "HTTP_403_NOT_USING_HTTPS": "Not using SSL/TLS", diff --git a/lib/api3/doc/security.md b/lib/api3/doc/security.md index 0fdf4c7d2aa..49a2505ca0e 100644 --- a/lib/api3/doc/security.md +++ b/lib/api3/doc/security.md @@ -27,22 +27,6 @@ There are two ways to authorize API calls: - 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) - ---- -### Client timestamps -As previously mentioned, a potential attacker cannot decrypt the captured messages, but he can send them back to the client/server at any later time. APIv3 is partially preventing this by the temporal validity of each secured API call. - - -The client must include his current timestamp to each call so that the server can compare it against its clock. If the timestamp difference is not within the limit, the request is considered invalid. The tolerance limit is set in minutes in the `API3_TIME_SKEW_TOLERANCE` environment variable. - -There are two ways to include the client timestamp to the call: -- use `now` query parameter with UNIX epoch millisecond timestamp, eg. `now=1565041446908` -- add HTTP `Date` header to the request, eg. `Date: Sun, 12 May 2019 07:49:58 GMT` - - -The client can check each server response in the same way, because each response contains a server timestamp in the HTTP *Date* header as well. - - --- APIv3 security is enabled by default, but it can be completely disabled for development and debugging purposes by setting the web environment variable `API3_SECURITY_ENABLE=false`. This setting is hazardous and it is strongly discouraged to be used for production purposes! diff --git a/lib/api3/doc/tutorial.md b/lib/api3/doc/tutorial.md index 73bc6c99f8d..50ab57c5b93 100644 --- a/lib/api3/doc/tutorial.md +++ b/lib/api3/doc/tutorial.md @@ -47,7 +47,7 @@ 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&now=${new Date().getTime()}`; +const auth = `token=testadmin-ad3b1f9d7b3f59d5`; request(`https://nsapiv3.herokuapp.com/api/v3/status?${auth}`, (error, response, body) => console.log(body)); @@ -86,7 +86,7 @@ Sample result: Sample GET `/entries` client code (to retrieve last 3 BG values): ```javascript const request = require('request'); -const auth = `token=testadmin-ad3b1f9d7b3f59d5&now=${new Date().getTime()}`; +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)); @@ -124,7 +124,7 @@ Sample result: Sample POST `/treatments` client code: ```javascript const request = require('request'); -const auth = `token=testadmin-ad3b1f9d7b3f59d5&now=${new Date().getTime()}`; +const auth = `token=testadmin-ad3b1f9d7b3f59d5`; const doc = { date: 1564591511232, // (new Date()).getTime(), app: 'AndroidAPS', @@ -158,7 +158,7 @@ Sample result: Sample GET `/treatments/{identifier}` client code: ```javascript const request = require('request'); -const auth = `token=testadmin-ad3b1f9d7b3f59d5&now=${new Date().getTime()}`; +const auth = `token=testadmin-ad3b1f9d7b3f59d5`; const identifier = '95e1a6e3-1146-5d6a-a3f1-41567cae0895'; request(`https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}?${auth}`, @@ -193,7 +193,7 @@ Sample result: Sample GET `/lastModified` client code (to get latest modification dates): ```javascript const request = require('request'); -const auth = `token=testadmin-ad3b1f9d7b3f59d5&now=${new Date().getTime()}`; +const auth = `token=testadmin-ad3b1f9d7b3f59d5`; request(`https://nsapiv3.herokuapp.com/api/v3/lastModified?${auth}`, (error, response, body) => console.log(body)); @@ -223,7 +223,7 @@ 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&now=${new Date().getTime()}`; +const auth = `token=testadmin-ad3b1f9d7b3f59d5`; const identifier = '95e1a6e3-1146-5d6a-a3f1-41567cae0895'; const doc = { date: 1564591511232, @@ -257,7 +257,7 @@ 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&now=${new Date().getTime()}`; +const auth = `token=testadmin-ad3b1f9d7b3f59d5`; const identifier = '95e1a6e3-1146-5d6a-a3f1-41567cae0895'; const doc = { insulin: 0.5 @@ -287,7 +287,7 @@ Sample result: Sample DELETE `/treatments/{identifier}` client code (to update `insulin` from 0.4 to 0.5): ```javascript const request = require('request'); -const auth = `token=testadmin-ad3b1f9d7b3f59d5&now=${new Date().getTime()}`; +const auth = `token=testadmin-ad3b1f9d7b3f59d5`; const identifier = '95e1a6e3-1146-5d6a-a3f1-41567cae0895'; request({ @@ -312,7 +312,7 @@ Sample result: Sample HISTORY `/treatments/history/{lastModified}` client code: ```javascript const request = require('request'); -const auth = `token=testadmin-ad3b1f9d7b3f59d5&now=${new Date().getTime()}`; +const auth = `token=testadmin-ad3b1f9d7b3f59d5`; const lastModified = 1564521267421; request(`https://nsapiv3.herokuapp.com/api/v3/treatments/history/${lastModified}?${auth}`, diff --git a/lib/api3/index.js b/lib/api3/index.js index e1cde9f2ae8..83db322a452 100644 --- a/lib/api3/index.js +++ b/lib/api3/index.js @@ -66,7 +66,6 @@ function configure (env, ctx) { app.set('enabledCollections', ['devicestatus', 'entries', 'food', 'profile', 'settings', 'treatments']); self.setENVTruthy('API3_SECURITY_ENABLE', apiConst.API3_SECURITY_ENABLE); - self.setENVTruthy('API3_TIME_SKEW_TOLERANCE', apiConst.API3_TIME_SKEW_TOLERANCE); self.setENVTruthy('API3_DEDUP_FALLBACK_ENABLED', apiConst.API3_DEDUP_FALLBACK_ENABLED); self.setENVTruthy('API3_CREATED_AT_FALLBACK_ENABLED', apiConst.API3_CREATED_AT_FALLBACK_ENABLED); self.setENVTruthy('API3_MAX_LIMIT', apiConst.API3_MAX_LIMIT); diff --git a/lib/api3/security.js b/lib/api3/security.js index 6d6afe21055..7adeefbc76b 100644 --- a/lib/api3/security.js +++ b/lib/api3/security.js @@ -1,10 +1,8 @@ 'use strict'; -const moment = require('moment') - , apiConst = require('./const.json') +const apiConst = require('./const.json') , _ = require('lodash') , shiroTrie = require('shiro-trie') - , dateTools = require('./shared/dateTools') , opTools = require('./shared/operationTools') ; @@ -13,37 +11,6 @@ function getRemoteIP (req) { return req.headers['x-forwarded-for'] || req.connection.remoteAddress; } -/** - * Check if Date header in HTTP request (or 'now' query parameter) is present and valid (with error response sending) - */ -function checkDateHeader (opCtx) { - - const { app, req, res } = opCtx; - - let dateString = req.header('Date'); - if (!dateString) { - dateString = req.query.now; - } - - if (!dateString) { - return opTools.sendJSONStatus(res, apiConst.HTTP.UNAUTHORIZED, apiConst.MSG.HTTP_401_MISSING_DATE); - } - - let dateMoment = dateTools.parseToMoment(dateString); - if (!dateMoment) { - return opTools.sendJSONStatus(res, apiConst.HTTP.UNAUTHORIZED, apiConst.MSG.HTTP_401_BAD_DATE); - } - - let nowMoment = moment(new Date()); - let diffMinutes = moment.duration(nowMoment.diff(dateMoment)).asMinutes(); - - if (Math.abs(diffMinutes) > app.get('API3_TIME_SKEW_TOLERANCE')) { - return opTools.sendJSONStatus(res, apiConst.HTTP.UNAUTHORIZED, apiConst.MSG.HTTP_401_DATE_OUT_OF_TOLERANCE); - } - - return true; -} - function authenticate (opCtx) { return new Promise(function promise (resolve, reject) { @@ -56,16 +23,6 @@ function authenticate (opCtx) { return resolve({ shiros: [ adminShiro ] }); } -// if (req.protocol !== 'https') { -// return reject( -// opTools.sendJSONStatus(res, apiConst.HTTP.FORBIDDEN, apiConst.MSG.HTTP_403_NOT_USING_HTTPS)); -// } - - const checkDateResult = checkDateHeader(opCtx); - if (checkDateResult !== true) { - return checkDateResult; - } - let token = ctx.authorization.extractToken(req); if (!token) { return reject( diff --git a/lib/api3/swagger.json b/lib/api3/swagger.json index a380be3e0f5..67a54d97d95 100644 --- a/lib/api3/swagger.json +++ b/lib/api3/swagger.json @@ -2,7 +2,7 @@ "openapi": "3.0.0", "info": { "title": "Nightscout API", - "description": "Nightscout API v3 is a component of cgm-remote-monitor project. It aims to provide lightweight, secured and HTTP REST compliant interface for your T1D treatment data exchange.\n\nAPI v3 uses these environment variables, among other things:\n- Security switch (optional, default = `true`)
API3_SECURITY_ENABLE=true
You can turn the whole security mechanism off, e.g. for debugging or development purposes, but this should never be set to false in production.\n\n- Number of minutes of acceptable time skew between client's and server's clock (optional, default = 5)
API3_TIME_SKEW_TOLERANCE=5
This security parameter is used for preventing anti-replay attacks, specifically when checking the time from `Date` header.\n\n- Maximum limit count of documents retrieved from single query
API3_MAX_LIMIT=1000
\n\n- Autopruning of obsolete documents (optional, default is only `DEVICESTATUS`=60)
API3_AUTOPRUNE_DEVICESTATUS=60\nAPI3_AUTOPRUNE_ENTRIES=365\nAPI3_AUTOPRUNE_TREATMENTS=120 
You can specify for which collections autopruning will be activated and length of retention period in days, e.g. \"Hold 60 days of devicestatus, automatically delete older documents, hold 365 days of treatments and entries, automatically delete older documents.\"\n\n- Fallback deduplication switch (optional, default = true)
API3_DEDUP_FALLBACK_ENABLED=true
API3 uses the `identifier` field for document identification and mutual distinction within a single collection. There is automatic deduplication implemented matching the equal `identifier` field. E.g. `CREATE` operation for document having the same `identifier` as another one existing in the database is automatically transformed into `UPDATE` operation of the document found in the database.\nDocuments not created via API v3 usually does not have any `identifier` field, but we would like to have some form of deduplication for them, too. This fallback deduplication is turned on by having set `API3_DEDUP_FALLBACK_ENABLED` to `true`. When searching the collection in database, the document is found to be a duplicate only when either he has equal `identifier` or he has no `identifier` and meets:
`devicestatus` collection: equal combination of `created_at` and `device`\n`entries` collection:      equal combination of `date` and `type`\n`food` collection:         equal `created_at`\n`profile` collection:      equal `created_at`\n`treatments` collection:   equal combination of `created_at` and `eventType` 
\n\n- Fallback switch for adding `created_at` field along the `date` field (optional, default = true)
API3_CREATED_AT_FALLBACK_ENABLED=true
Standard APIv3 document model uses only `date` field for storing a timestamp of the event recorded by the document. But there is a fallback option to fill `created_at` field as well automatically on each insert/update, just to keep all older components working.", + "description": "Nightscout API v3 is a component of cgm-remote-monitor project. It aims to provide lightweight, secured and HTTP REST compliant interface for your T1D treatment data exchange.\n\nAPI v3 uses these environment variables, among other things:\n- Security switch (optional, default = `true`)
API3_SECURITY_ENABLE=true
You can turn the whole security mechanism off, e.g. for debugging or development purposes, but this should never be set to false in production.\n\n- Maximum limit count of documents retrieved from single query
API3_MAX_LIMIT=1000
\n\n- Autopruning of obsolete documents (optional, default is only `DEVICESTATUS`=60)
API3_AUTOPRUNE_DEVICESTATUS=60\nAPI3_AUTOPRUNE_ENTRIES=365\nAPI3_AUTOPRUNE_TREATMENTS=120 
You can specify for which collections autopruning will be activated and length of retention period in days, e.g. \"Hold 60 days of devicestatus, automatically delete older documents, hold 365 days of treatments and entries, automatically delete older documents.\"\n\n- Fallback deduplication switch (optional, default = true)
API3_DEDUP_FALLBACK_ENABLED=true
API3 uses the `identifier` field for document identification and mutual distinction within a single collection. There is automatic deduplication implemented matching the equal `identifier` field. E.g. `CREATE` operation for document having the same `identifier` as another one existing in the database is automatically transformed into `UPDATE` operation of the document found in the database.\nDocuments not created via API v3 usually does not have any `identifier` field, but we would like to have some form of deduplication for them, too. This fallback deduplication is turned on by having set `API3_DEDUP_FALLBACK_ENABLED` to `true`. When searching the collection in database, the document is found to be a duplicate only when either he has equal `identifier` or he has no `identifier` and meets:
`devicestatus` collection: equal combination of `created_at` and `device`\n`entries` collection:      equal combination of `date` and `type`\n`food` collection:         equal `created_at`\n`profile` collection:      equal `created_at`\n`treatments` collection:   equal combination of `created_at` and `eventType` 
\n\n- Fallback switch for adding `created_at` field along the `date` field (optional, default = true)
API3_CREATED_AT_FALLBACK_ENABLED=true
Standard APIv3 document model uses only `date` field for storing a timestamp of the event recorded by the document. But there is a fallback option to fill `created_at` field as well automatically on each insert/update, just to keep all older components working.", "contact": { "name": "NS development discussion channel", "url": "https://gitter.im/nightscout/public" @@ -11,7 +11,7 @@ "name": "AGPL 3", "url": "https://www.gnu.org/licenses/agpl.txt" }, - "version": "3.0.2" + "version": "3.0.3" }, "servers": [ { @@ -49,29 +49,6 @@ "$ref": "#/components/schemas/paramCollection" } }, - { - "name": "Date", - "in": "header", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", - "required": false, - "style": "simple", - "explode": false, - "schema": { - "type": "string" - } - }, - { - "name": "now", - "in": "query", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, { "name": "token", "in": "query", @@ -198,7 +175,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -265,29 +242,6 @@ "$ref": "#/components/schemas/paramCollection" } }, - { - "name": "Date", - "in": "header", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", - "required": false, - "style": "simple", - "explode": false, - "schema": { - "type": "string" - } - }, - { - "name": "now", - "in": "query", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, { "name": "token", "in": "query", @@ -359,7 +313,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -439,29 +393,6 @@ "$ref": "#/components/schemas/paramIdentifier" } }, - { - "name": "Date", - "in": "header", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", - "required": false, - "style": "simple", - "explode": false, - "schema": { - "type": "string" - } - }, - { - "name": "now", - "in": "query", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, { "name": "token", "in": "query", @@ -542,7 +473,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -630,29 +561,6 @@ "$ref": "#/components/schemas/paramIdentifier" } }, - { - "name": "Date", - "in": "header", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", - "required": false, - "style": "simple", - "explode": false, - "schema": { - "type": "string" - } - }, - { - "name": "now", - "in": "query", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, { "name": "token", "in": "query", @@ -724,7 +632,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -822,29 +730,6 @@ "$ref": "#/components/schemas/paramIdentifier" } }, - { - "name": "Date", - "in": "header", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", - "required": false, - "style": "simple", - "explode": false, - "schema": { - "type": "string" - } - }, - { - "name": "now", - "in": "query", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, { "name": "token", "in": "query", @@ -880,7 +765,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -958,29 +843,6 @@ "$ref": "#/components/schemas/paramIdentifier" } }, - { - "name": "Date", - "in": "header", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", - "required": false, - "style": "simple", - "explode": false, - "schema": { - "type": "string" - } - }, - { - "name": "now", - "in": "query", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, { "name": "token", "in": "query", @@ -1037,7 +899,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -1127,29 +989,6 @@ "$ref": "#/components/schemas/paramCollection" } }, - { - "name": "Date", - "in": "header", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", - "required": false, - "style": "simple", - "explode": false, - "schema": { - "type": "string" - } - }, - { - "name": "now", - "in": "query", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, { "name": "token", "in": "query", @@ -1248,7 +1087,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -1330,29 +1169,6 @@ "format": "int64" } }, - { - "name": "Date", - "in": "header", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", - "required": false, - "style": "simple", - "explode": false, - "schema": { - "type": "string" - } - }, - { - "name": "now", - "in": "query", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, { "name": "token", "in": "query", @@ -1440,7 +1256,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -1530,7 +1346,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -1569,29 +1385,6 @@ "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": "Date", - "in": "header", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", - "required": false, - "style": "simple", - "explode": false, - "schema": { - "type": "string" - } - }, - { - "name": "now", - "in": "query", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, { "name": "token", "in": "query", @@ -1616,7 +1409,7 @@ } }, "401": { - "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -2559,7 +2352,7 @@ } }, "401Unauthorized": { - "description": "The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy.", + "description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "content": { "application/json": { "schema": { @@ -2713,29 +2506,6 @@ } }, "parameters": { - "dateHeader": { - "name": "Date", - "in": "header", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `now` query parameter.\nExample:\n\n
Date: Wed, 17 Oct 2018 05:13:00 GMT
", - "required": false, - "style": "simple", - "explode": false, - "schema": { - "type": "string" - } - }, - "nowParam": { - "name": "now", - "in": "query", - "description": "Timestamp (defined by client's clock) when the HTTP request was constructed on client. This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. This can be set alternatively in `Date` header.\n\nExample:\n\n
now=1525383610088
", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, "tokenParam": { "name": "token", "in": "query", diff --git a/lib/api3/swagger.yaml b/lib/api3/swagger.yaml index 56175263924..c9b764a7409 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.2" + version: 3.0.3 title: Nightscout API contact: name: NS development discussion channel @@ -22,11 +22,6 @@ info: but this should never be set to false in production. - - Number of minutes of acceptable time skew between client's and server's clock (optional, default = 5) -
API3_TIME_SKEW_TOLERANCE=5
- This security parameter is used for preventing anti-replay attacks, specifically when checking the time from `Date` header. - - - Maximum limit count of documents retrieved from single query
API3_MAX_LIMIT=1000
@@ -80,8 +75,6 @@ paths: schema: $ref: '#/components/schemas/paramCollection' - - $ref: '#/components/parameters/dateHeader' - - $ref: '#/components/parameters/nowParam' - $ref: '#/components/parameters/tokenParam' ###################################################################################### @@ -203,8 +196,6 @@ paths: schema: $ref: '#/components/schemas/paramIdentifier' - - $ref: '#/components/parameters/dateHeader' - - $ref: '#/components/parameters/nowParam' - $ref: '#/components/parameters/tokenParam' ###################################################################################### @@ -417,8 +408,6 @@ paths: schema: $ref: '#/components/schemas/paramCollection' - - $ref: '#/components/parameters/dateHeader' - - $ref: '#/components/parameters/nowParam' - $ref: '#/components/parameters/tokenParam' get: @@ -486,8 +475,6 @@ paths: type: integer format: int64 - - $ref: '#/components/parameters/dateHeader' - - $ref: '#/components/parameters/nowParam' - $ref: '#/components/parameters/tokenParam' get: @@ -574,8 +561,6 @@ paths: ###################################################################################### /lastModified: parameters: - - $ref: '#/components/parameters/dateHeader' - - $ref: '#/components/parameters/nowParam' - $ref: '#/components/parameters/tokenParam' get: @@ -608,41 +593,6 @@ components: parameters: - dateHeader: - in: header - name: Date - schema: - type: string - required: false - description: - Timestamp (defined by client's clock) when the HTTP request was constructed on client. - This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. - This can be set alternatively in `now` query parameter. - - Example: - - -
Date: Wed, 17 Oct 2018 05:13:00 GMT
- - - nowParam: - in: query - name: now - schema: - type: integer - format: int64 - required: false - description: - Timestamp (defined by client's clock) when the HTTP request was constructed on client. - This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. - This can be set alternatively in `Date` header. - - - Example: - - -
now=1525383610088
- tokenParam: in: query @@ -937,7 +887,7 @@ components: example: 400 401Unauthorized: - description: The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy. + description: The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy. content: application/json: schema: diff --git a/tests/api3.security.test.js b/tests/api3.security.test.js index 7cd811acfe6..df0928ffe9c 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') - , semver = require('semver') , moment = require('moment') ; require('should'); @@ -33,91 +32,6 @@ describe('Security of REST API3', function() { }); -// it('should require HTTPS', async () => { -// if (semver.gte(process.version, '10.0.0')) { -// let res = await request(self.http.baseUrl) // hangs on 8.x.x (no reason why) -// .get('/api/v3/test') -// .expect(403); -// -// res.body.status.should.equal(403); -// res.body.message.should.equal(apiConst.MSG.HTTP_403_NOT_USING_HTTPS); -// } -// }); - - - it('should require Date header', async () => { - let res = await request(self.https.baseUrl) - .get('/api/v3/test') - .expect(401); - - res.body.status.should.equal(401); - res.body.message.should.equal(apiConst.MSG.HTTP_401_MISSING_DATE); - }); - - - it('should validate Date header syntax', async () => { - let res = await request(self.https.baseUrl) - .get('/api/v3/test') - .set('Date', 'invalid date header') - .expect(401); - - res.body.status.should.equal(401); - res.body.message.should.equal(apiConst.MSG.HTTP_401_BAD_DATE); - }); - - - it('should reject Date header out of tolerance', async () => { - const oldDate = new Date((new Date() * 1) - 2 * 3600 * 1000) - , futureDate = new Date((new Date() * 1) + 2 * 3600 * 1000); - - let res = await request(self.https.baseUrl) - .get('/api/v3/test') - .set('Date', oldDate.toUTCString()) - .expect(401); - - res.body.status.should.equal(401); - res.body.message.should.equal(apiConst.MSG.HTTP_401_DATE_OUT_OF_TOLERANCE); - - res = await request(self.https.baseUrl) - .get('/api/v3/test') - .set('Date',futureDate.toUTCString()) - .expect(401); - - res.body.status.should.equal(401); - res.body.message.should.equal(apiConst.MSG.HTTP_401_DATE_OUT_OF_TOLERANCE); - }); - - - it('should reject invalid now ABC', async () => { - let res = await request(self.https.baseUrl) - .get(`/api/v3/test?now=ABC`) - .expect(401); - - res.body.status.should.equal(401); - res.body.message.should.equal('Bad Date header'); - }); - - - it('should reject invalid now -1', async () => { - let res = await request(self.https.baseUrl) - .get(`/api/v3/test?now=-1`) - .expect(401); - - res.body.status.should.equal(401); - res.body.message.should.equal('Bad Date header'); - }); - - - it('should reject invalid now - illegal format', async () => { - let res = await request(self.https.baseUrl) - .get(`/api/v3/test?now=2019-20-60T50:90:90`) - .expect(401); - - res.body.status.should.equal(401); - res.body.message.should.equal('Bad Date header'); - }); - - it('should require token', async () => { let res = await request(self.https.baseUrl) .get('/api/v3/test') @@ -164,8 +78,8 @@ describe('Security of REST API3', function() { .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()}`) diff --git a/tests/fixtures/api3/instance.js b/tests/fixtures/api3/instance.js index 2b19aad6145..358b42bb2c2 100644 --- a/tests/fixtures/api3/instance.js +++ b/tests/fixtures/api3/instance.js @@ -43,15 +43,15 @@ function configure () { self.addSecuredOperations = function addSecuredOperations (instance) { - instance.get = (url) => request(instance.baseUrl).get(url).set('Date', new Date().toUTCString()); + instance.get = (url) => request(instance.baseUrl).get(url); - instance.post = (url) => request(instance.baseUrl).post(url).set('Date', new Date().toUTCString()); + instance.post = (url) => request(instance.baseUrl).post(url); - instance.put = (url) => request(instance.baseUrl).put(url).set('Date', new Date().toUTCString()); + instance.put = (url) => request(instance.baseUrl).put(url); - instance.patch = (url) => request(instance.baseUrl).patch(url).set('Date', new Date().toUTCString()); + instance.patch = (url) => request(instance.baseUrl).patch(url); - instance.delete = (url) => request(instance.baseUrl).delete(url).set('Date', new Date().toUTCString()); + instance.delete = (url) => request(instance.baseUrl).delete(url); };