Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,9 @@ fabric.properties
/target
**/*.rs.bk

enigma-types.h

# exclude docker mongodb data
backend/data/

enigma-types.h

199 changes: 199 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# COVID-19 Self-reporting API server User IDs

![GitHub pull request check state](https://img.shields.io/github/status/s/pulls/enigmampc/SafeTrace/52)
![Docker-compose version](https://img.shields.io/badge/Docker--Ccompose-%5E1.21.2-brightgreen)
![node-current (tag)](https://img.shields.io/node/v/nodemon/latest)
![NPM version](https://img.shields.io/badge/NPM-6.14.4-verde)
![Mongoose version](https://img.shields.io/badge/MONGO-%5E5.9.4-green)


## Requirements

You need these packages installed on your machine:
* [Docker and docker-compose](https://docs.docker.com/compose/install/)
* [NPM](https://www.npmjs.com/get-npm)
* [nodejs](https://nodejs.org/en/)

## Installation

Clone the project and run these commands.

```
$ cd <your_project_folder>/backend/app/

$ npm install

$ cd ..

$ docker-compose up --build
```

The node js project will run on http://localhost:4080/

admin-mongo interface will be displayed on http://localhost:8082/
(this is deactivated by default. You need to uncomment lines in the docker-compose.yml to activate it)

Internal Mongo DB connection string
`mongodb://mongo/safetrace`

External Mongo DB connection string (Robo3t)
`mongodb://localhost:10975/safetrace` *In the docker-compose.yml file the port configuration is mapping the port 10975 to 27017 just to avoid expose the normal mongodb port.*

.env file has some configurations
- PORT: port where run nodejs
- MONGOURI: path to access to mongoDB database (`mongodb://mongo/safetrace`)
- GOOGLE_CLIENT_ID= client id path to register and validate users for Google sign up service. More innformation [here](https://developers.google.com/identity/sign-in/web/backend-auth).

app/config/config.js
- >secret: 'youSecretWord'
- This configuration is used to encrypt the user id. It must be changed for every different app. *This is a temporary solution for the MVP version.*

# Google Sign In integration

## /user/glogin

This endpoint registers new Google users if it don't exist. And logs in the user. It returns the internal token that you have to use for the next requests (header `x-access-token`)

```
curl --location --request POST 'http://localhost:4080/user/glogin' \
--header 'Content-Type: application/json' \
--data-raw '{
"token": "eyJhbGciO0eXAiOi...LeLIU9ZMrVoCV2xA"
}'
```
**Returns**

```
{
"token": "eyJhbGciOiJIUzI1NiIs...yiV2CNK12IVGESQ"
}
```

# JWT User endpoints

## /user/signup

```
curl --location --request POST 'https://localhost:4080/user/signup' \
--header 'Content-Type: application/json' \
--data-raw '{
"username": "username",
"email": "name@domain.com",
"password": "yourpass",
"agreeToBeNotified": true
}'
```
**Returns**
```
{
"token": "eyJhbGciOiJIUzI1NiI....bJI7QTsgyM3Qk0"
}
```

## /user/login

```
curl --location --request POST 'http://localhost:4080/user/login' \
--header 'Content-Type: application/json' \
--data-raw '{
"email": "name@domain.com",
"password": "yourpass"
}'
```
**Returns**
```
{
"token": "eyJhbGciOiJIUzI1NiIs....R-sJ9iHNUde0"
}
```

## /user/me

```
curl --location --request GET 'http://localhost:4080/user/me' \
--header 'x-access-token: eyJhbGci....blSXAPm0'

```
**Returns**
```
{
"agreeToBeNotified": false,
"userType": 1,
"createdAt": "2020-04-02T10:16:12.090Z",
"_id": "5e85bf05420bac7bcfece08f",
"username": "username",
"email": "name@domain.com",
"password": "$2a$10$21w/RANQMJoc2Ge6FpcSSOCpY1S2ae6li5dv5xNeQEzewphkneGcS",
"idUser": 1,
"encryptedUserId": "5f6451160f76aac8a02493787dc940a0057746c6401cc49757664ce1032b8450",
"__v": 0
}
```

`encryptedUserId` returns the encrypted email as user id.

# Report

## POST: /report

This let to add a result test to the report table.

```
curl --location --request POST 'http://localhost:4080/report' \
--header 'x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNWU4ODhhZDBlZGEwN2U2ZGFlYjY0ODBiIn0sImlhdCI6MTU4NjAxNjA2MiwiZXhwIjoxNTg2MDE5NjYyfQ.hmxFFAz1P80Yq4Q2iA6D8IpCOhI5_7xkfZYWDkQOHK4' \
--header 'Content-Type: application/json' \
--data-raw '{
"idUser": "'9365df4e9acef8b63b45dc3534491225ac32630abe6991f6bf5a74c9803412fc'",
"testDate": "03/02/2020",
"testResult": 0
}'
```
**Returns**

```
{
"report": {
"createdAt": "2020-04-04T16:04:00.725Z",
"_id": "5e88b01316714c664f5ef11e",
"idUser": 1,
"testDate": "2020-03-02T03:00:00.000Z",
"testResult": 0,
"idReport": 13,
"__v": 0
}
}
```

## GET: /report/{idUser}

Get the list of reported tests


```
curl --location --request GET 'http://localhost:4080/report/9365df4e9acef8b63b45dc3534491225ac32630abe6991f6bf5a74c9803412fc' \
--header 'x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNWU4ODhhZDBlZGEwN2U2ZGFlYjY0ODBiIn0sImlhdCI6MTU4NjI4Njg4MiwiZXhwIjoxNTg2MjkwNDgyfQ.zVNwkDZCXtlxRJnDhonptMxbgpngrB3T7cNIy8vde_I'
```
**Returns**

```
{
"reports": [
{
"createdAt": "2020-04-07T19:24:31.388Z",
"_id": "5e8cd38dc43d8007408e600d",
"idUser": "9365df4e9acef8b63b45dc3534491225ac32630abe6991f6bf5a74c9803412fc",
"testDate": "2020-03-04T03:00:00.000Z",
"testResult": 1,
"idReport": 17,
"__v": 0
},
{
...
}
]
}
```

## LICENSE

The code in this repository is released under the [MIT License](https://github.com/cmalfesi/SafeTrace/blob/master/LICENSE).
5 changes: 5 additions & 0 deletions backend/app/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
COMPOSE_CONVERT_WINDOWS_PATHS=1
PORT=4080
MONGOURI=mongodb://mongo/safetrace
NODE_ENV=development
GOOGLE_CLIENT_ID="119469794689-ojm97chi9mv1148av7d1lc8ghjkelddl.apps.googleusercontent.com"
18 changes: 18 additions & 0 deletions backend/app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#define the latest nodejs image to build from
FROM node:latest
RUN mkdir -p /usr/src/apiServerUsers

#create a working directory
WORKDIR /usr/src/apiServerUsers
RUN npm install -g nodemon --save

#copy package.json file under the working directory
COPY package.json /usr/src/apiServerUsers/
RUN npm install

#copy all your files under the working directory
COPY . /usr/src/apiServerUsers/

EXPOSE 4080
#start nodejs server
CMD nodemon server.js
4 changes: 4 additions & 0 deletions backend/app/config/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//secret word for validating tokens
module.exports = {
secret: 'safeTraceEnigma245237401975' //this value must be changed for every different app
};
22 changes: 22 additions & 0 deletions backend/app/helpers/crypto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Nodejs encryption with CTR
const crypto = require('crypto');
const algorithm = 'aes-256-cbc';
const key = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);

exports.encrypt = (text) => {
let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return { iv: iv.toString('hex'), encryptedData: encrypted.toString('hex') };
}

exports.decrypt = (text) => {
let iv = Buffer.from(text.iv, 'hex');
let encryptedText = Buffer.from(text.encryptedData, 'hex');
let decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv);
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}

21 changes: 21 additions & 0 deletions backend/app/middleware/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const jwt = require("jsonwebtoken");
const config = require('../config/config.js');


module.exports = function(req, res, next) {
let userType = req.headers['auth-user-type'] || 0;
let token = req.headers['x-access-token'] || req.headers['authorization']; // Express headers are auto converted to lowercase

if (!token) return res.status(401).json({ message: "Auth Error" });

try {
const decoded = jwt.verify(token, config.secret);
req.user = decoded.user;
next();
} catch (e) {
console.error(e);
res.status(500).send({ message: "Invalid Token" });
}
};


33 changes: 33 additions & 0 deletions backend/app/models/report.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var autoIncrement = require('mongoose-easy-auto-increment');

var ReportSchema = new Schema({
idReport: {
type: Number,
validate: {
validator: Number.isInteger,
message: '{VALUE} is not an integer value'
}
},
idUser: {
type: String,
required: true
},
testDate: {
type: Date,
requered: true
},
testResult: {
type: Number, //0 = not tested, no symptons, 1 positive, 2 high risk symptoms
required: true
},
createdAt: {
type: Date,
default: Date.now()
}
});

ReportSchema.plugin(autoIncrement, { field: 'idReport', collection: 'counters' });
var ReportModel = mongoose.model('report', ReportSchema);
module.exports = ReportModel;
50 changes: 50 additions & 0 deletions backend/app/models/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var autoIncrement = require('mongoose-easy-auto-increment');


var UserSchema = new Schema({
idUser: {
type: Number,
validate: {
validator: Number.isInteger,
message: '{VALUE} is not an integer value'
}
},
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
agreeToBeNotified: {
type: Boolean,
default: false
},
encryptedUserId: {
type: String,
required: true
},
userType: {
type: Number, //0= internal JWT, 1= Google
default: 0,
validate: {
validator: Number.isInteger,
message: '{VALUE} is not an integer value'
}
},
createdAt: {
type: Date,
default: Date.now()
}
});

UserSchema.plugin(autoIncrement, { field: 'idUser', collection: 'counters' });
var UserModel = mongoose.model('user', UserSchema);
module.exports = UserModel;
7 changes: 7 additions & 0 deletions backend/app/nodemon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"env": {
"NODE_ENV":"development",
"COMPOSE_CONVERT_WINDOWS_PATHS":1
},
"ext": "js json ejs"
}
Loading