Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api3 remove Date header #6855

Merged
merged 11 commits into from
Feb 10, 2021
6 changes: 1 addition & 5 deletions lib/api3/const.json
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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",
Expand Down
16 changes: 0 additions & 16 deletions lib/api3/doc/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!
18 changes: 9 additions & 9 deletions lib/api3/doc/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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}`,
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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({
Expand All @@ -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}`,
Expand Down
1 change: 0 additions & 1 deletion lib/api3/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
45 changes: 1 addition & 44 deletions lib/api3/security.js
Original file line number Diff line number Diff line change
@@ -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')
;

Expand All @@ -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) {
Expand All @@ -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(
Expand Down
Loading