diff --git a/.gitignore b/.gitignore
index 1f7a204..0c31d6e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@ build/
node_modules/
.env
.vscode/settings.json
+org1.example.com
+
diff --git a/README.md b/README.md
index ef8b007..d497bd3 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,136 @@
-# Kinto-storage-service
+
-## Run
+
+
Kinto storage service
+
+
+ UADE Informatics Engineering thesis project - 2022
+
-`npm run start`
-TODO: Add watch mode for dev
+
+ Kinto storage service, handles strorage and authentication operations.
+
+
+
+
-### first version of ipfs connection
+
-start ipfs node in your computer with
-`jsipfs daemon`
+# :notebook_with_decorative_cover: Table of Contents
-make sure it's reachable (`localhost:5001/webui`) and, if not, configure CORS properly (there are instructions there)
+- [About the Project](#star2-about-the-project)
+ - [Tech Stack](#space_invader-tech-stack)
+ - [Features](#dart-features)
+- [Setup](#Setup)
+ - [Prerequisites](#bangbang-prerequisites)
+- [Usage](#eyes-usage)
+- [License](#warning-license)
+- [Contact](#handshake-contact)
+- [Acknowledgements](#gem-acknowledgements)
-have fun
+## :star2: About the Project
-## Docs
+This service manages storage operatiuons, hyperledger blockchain connection, auuthentication and interactions, and IPFS network operations.
-TODO: Add Swagger support
+### :space_invader: Tech Stack
-## Reference
+
-https://www.toptal.com/express-js/nodejs-typescript-rest-api-pt-1
+### :dart: Features
+
+- Authenticates users with hyperledger blockchain
+- Stoores and retreives files from IPFS network
+- Submit operations into hyperledger blockchain
+
+## :toolbox: Setup
+
+### :bangbang: Prerequisites
+
+- **Node** This project uses node and npm as package manager, make sure it is installed.
+
+```bash
+ node -v
+ npm -v
+```
+
+- **ipfs** This project uses IPFS to store files connect to an IPFS network or create your own by running the following command
+
+```bash
+jsipfs daemon
+```
+
+### :key: Environment Variables
+
+To run this project, you will need to add the following environment variables to your .env file, here it is an example of a localhost configuiration
+
+```bash
+PORT=8081
+
+ENV=localhost
+CCP_PATH=./org1.example.com/connection-org1.json
+WALLET_PATH=./wallet
+ADMIN_WALLET=admin
+ADMIN_WALLET_SECRET=adminpw
+CA_ORG_ID=ca.org1.example.com
+
+IPFS_API=http://127.0.0.1:5001/api/v0
+```
+
+## :gear: Usage
+
+Clone the project
+
+```bash
+ git clone https://github.com/K-nto/Kinto-network-status-service.git
+```
+
+Go to the project directory
+
+```bash
+ cd Kinto-network-status-service
+```
+
+Install dependencies.
+
+```bash
+ npm install
+```
+
+Start the service.
+
+```bash
+ npm run start
+```
+
+## :warning: License
+
+Distributed under the no License. See LICENSE.txt for more information.
+
+
+
+## :handshake: Contact
+
+Federico Javier Parodi - Fedejp - [Linkedin](https://www.linkedin.com/in/fedejp) - [Github](https://github.com/Fedejp)
+
+Carlos Santiago Yanzon - Bizk - [Linkedin](https://www.linkedin.com/in/carlos-santiago-yanzon/) - [Github](https://github.com/bizk)
+
+Project Link: [https://github.com/K-nto](https://github.com/K-nto)
+
+## :gem: Acknowledgements
+
+We thank and aknowledge the authors of these resources for their work.
+
+- [Awesome README](https://github.com/matiassingers/awesome-readme)
+- [Emoji Cheat Sheet](https://github.com/ikatyang/emoji-cheat-sheet/blob/master/README.md#travel--places)
+- [Readme Template](https://github.com/othneildrew/Best-README-Template)
diff --git a/resources/Kintoisologo.png b/resources/Kintoisologo.png
new file mode 100644
index 0000000..83cb207
Binary files /dev/null and b/resources/Kintoisologo.png differ
diff --git a/src/common/authorization.service.ts b/src/common/authorization.service.ts
new file mode 100644
index 0000000..b5c4459
--- /dev/null
+++ b/src/common/authorization.service.ts
@@ -0,0 +1,18 @@
+import {testUsers} from '../users/testUsers';
+
+/** Returns true if the request is originated by a valid public/private key pair
+ * Probably better in the HL Authenticator class
+ */
+export const authorized = (userId: string, authToken: string = '') => {
+ // @TODO: authorize for realsies
+ // call HL authenticator here or sth
+ return (
+ authToken &&
+ testUsers.find(user => user.address === userId) &&
+ userId == authToken
+ );
+};
+
+export const getUserInfo = (walletAddress: string) => {
+ return testUsers.find(user => user.address === walletAddress);
+};
diff --git a/src/files/files.controller.ts b/src/files/files.controller.ts
index 04de24f..bf63501 100644
--- a/src/files/files.controller.ts
+++ b/src/files/files.controller.ts
@@ -2,9 +2,10 @@ import fileUpload from 'express-fileupload';
import {StorageOperationController} from '../hyperledger/StorageOperationController';
import ipfsService from '../ipfs/ipfs.service';
import {KFSEntry} from './files.interfaces';
+import {IEncryptedFile} from './interfaces/IEncryptedFile.interface';
class FilesController {
- async createFile(userId: string, file: fileUpload.UploadedFile) {
+ async createFile(userId: string, file: IEncryptedFile) {
console.log(
`[INFO] files.controller - createFile: Create file for user with id ${userId}`
);
diff --git a/src/files/files.routes.config.ts b/src/files/files.routes.config.ts
index 7a63c4a..f247694 100644
--- a/src/files/files.routes.config.ts
+++ b/src/files/files.routes.config.ts
@@ -1,8 +1,10 @@
import {Application, Request, Response, NextFunction} from 'express';
import fileUpload from 'express-fileupload';
+import {authorized} from '../common/authorization.service';
import {CommonRoutesConfig} from '../common/common.routes.config';
import {FILES, USERS} from '../common/common.routes.consts';
import filesController from './files.controller';
+import {IEncryptedFile} from './interfaces/IEncryptedFile.interface';
export class FilesRoutes extends CommonRoutesConfig {
constructor(app: Application) {
@@ -14,12 +16,20 @@ export class FilesRoutes extends CommonRoutesConfig {
.route(`/${USERS}/:userId/${FILES}`)
.all((req: Request, res: Response, next: NextFunction) => {
// Middleware executed on every route. @TODO: Validation @TODO: User authentication @TODO: Register on Hyperledger
- console.debug('[Hyperledeger] Authenticated user: ', req.params.userId);
+ if (!authorized(req.params.userId, req.headers.authorization)) {
+ console.debug(
+ '[Authorization] Failed authentication for user: ',
+ req.params.userId
+ );
+ res.status(403).send('Invalid credentials');
+ return;
+ }
console.debug(
- '[Hyperledeger] Generated transaction: ',
- Math.random().toString(16).substr(16)
+ '[Authorization] Authenticated user: ',
+ req.params.userId
);
+
next();
})
.get((req: Request, res: Response) =>
@@ -31,11 +41,18 @@ export class FilesRoutes extends CommonRoutesConfig {
)
)
.post((req: Request, res: Response) => {
- const file = req.files?.file;
+ const file: IEncryptedFile = {...req.body};
- if (!file) res.status(400).send('Missing file');
+ if (
+ !file ||
+ !file.address ||
+ !file.fileData ||
+ !file.name ||
+ !file.type
+ )
+ return res.status(400).send('Missing file');
- filesController
+ return filesController
.createFile(req.params.userId, file)
.then(result => res.status(201).send(result))
.catch(error =>
@@ -85,7 +102,7 @@ export class FilesRoutes extends CommonRoutesConfig {
const userId = req.body.userId;
if (!userId) res.status(400).send('Missing userId');
- filesController
+ return filesController
.deleteFile(userId, fileName)
.then(message => res.status(200).send(message))
.catch(error =>
diff --git a/src/files/interfaces/IEncryptedFile.interface.ts b/src/files/interfaces/IEncryptedFile.interface.ts
new file mode 100644
index 0000000..f6a70a5
--- /dev/null
+++ b/src/files/interfaces/IEncryptedFile.interface.ts
@@ -0,0 +1,6 @@
+export interface IEncryptedFile {
+ name: string;
+ type: string;
+ address: string;
+ fileData: string;
+}
diff --git a/src/hyperledger/HyperledgerController.ts b/src/hyperledger/HyperledgerController.ts
index 5980ce8..fcb518a 100644
--- a/src/hyperledger/HyperledgerController.ts
+++ b/src/hyperledger/HyperledgerController.ts
@@ -78,7 +78,7 @@ export class HyperledgerController {
);
const resultBytes =
transactionArgs.length > 0
- ? await contract.evaluateTransaction(transaction, ...transactionArgs)
+ ? await contract.submitTransaction(transaction, ...transactionArgs)
: await contract.evaluateTransaction(transaction);
// eslint-disable-next-line node/no-unsupported-features/node-builtins
diff --git a/src/hyperledger/StorageOperationController.ts b/src/hyperledger/StorageOperationController.ts
index 824da37..7b4b3a2 100644
--- a/src/hyperledger/StorageOperationController.ts
+++ b/src/hyperledger/StorageOperationController.ts
@@ -54,7 +54,7 @@ export class StorageOperationController {
'kinto',
'createFileOperation',
fileHash,
- walletAddress,
+ walletAddress.toString(),
operation
);
console.log('[DEBUG] createFileOperation:', transactionResultPayload);
diff --git a/src/ipfs/ipfs.service.ts b/src/ipfs/ipfs.service.ts
index fc1e26c..2dc3ebf 100644
--- a/src/ipfs/ipfs.service.ts
+++ b/src/ipfs/ipfs.service.ts
@@ -3,9 +3,10 @@ import {MFSEntry} from 'ipfs-core-types/src/files';
import {create, IPFSHTTPClient} from 'ipfs-http-client';
import all from 'it-all';
import {KFSEntry} from '../files/files.interfaces';
-/**
- * @TODO: Better error handling
- */
+import {IEncryptedFile} from '../files/interfaces/IEncryptedFile.interface';
+
+const IPFS_DEFAULT_URL = 'http://127.0.0.1:5001/api/v0';
+
class IPFSService {
private static ipfsHttpClient: IPFSHTTPClient;
/**
@@ -17,7 +18,7 @@ class IPFSService {
// @TBD: Network url
if (!IPFSService.ipfsHttpClient)
IPFSService.ipfsHttpClient = create({
- url: 'http://127.0.0.1:5002/api/v0',
+ url: process.env.IPFS_API ?? IPFS_DEFAULT_URL,
});
}
@@ -33,14 +34,17 @@ class IPFSService {
*/
public async createFile(
userId: string,
- file: UploadedFile
+ file: IEncryptedFile
): Promise {
const filePath = `/${file.name}`;
- console.log('[DEBUG] IPFSService - createFile', userId, file);
-
- await IPFSService.ipfsHttpClient.files.write(filePath, file.data, {
- create: true,
- });
+ console.log('[DEBUG] IPFSService - createFile', userId, file.name);
+ await IPFSService.ipfsHttpClient.files.write(
+ filePath,
+ JSON.stringify(file),
+ {
+ create: true,
+ }
+ );
return await this.listFiles(filePath);
}
@@ -70,8 +74,7 @@ class IPFSService {
for await (const chunk of IPFSService.ipfsHttpClient.cat(cid)) {
fileChunks.push(chunk);
}
- return Buffer.concat(fileChunks);
- // return Buffer.from(fileChunks.toString())
+ return Buffer.concat(fileChunks).toString().split('}')[0].concat('}');
}
/**
* Overwrites a file that already exists (AKA, has the same name) in the root dir
diff --git a/src/nodes/nodes.routes.config.ts b/src/nodes/nodes.routes.config.ts
deleted file mode 100644
index 06fea7c..0000000
--- a/src/nodes/nodes.routes.config.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import {Application, Request, Response, NextFunction} from 'express';
-import {CommonRoutesConfig} from '../common/common.routes.config';
-import {NODES, USERS} from '../common/common.routes.consts';
-
-export class NodesRoutes extends CommonRoutesConfig {
- constructor(app: Application) {
- super(app, 'NodesRoutes');
- }
- configureRoutes() {
- this.app
- .route(`/${USERS}/:userId/${NODES}/`)
- .all((req: Request, res: Response, next: NextFunction) => {
- // Middleware executed on every route. @TODO: Validation @TODO: Authentication @TODO: Hyperledger (some)
- next();
- })
- .get((req: Request, res: Response) => {
- res.status(200).send(`TODO: GET node List for ${req.params.userId}`);
- })
- .post((req: Request, res: Response) => {
- res.status(200).send(`TODO: POST new node for ${req.params.userId}`);
- });
-
- this.app
- .route(`/${USERS}/:userId/${NODES}/:nodeId`)
- .all((req: Request, res: Response, next: NextFunction) => {
- // Middleware executed on every route. @TODO: Validation @TODO: Authentication @TODO: Hyperledger (some)
- next();
- })
- .get((req: Request, res: Response) => {
- res.status(200).send(`TODO: GET node info for ${req.params.nodeId}`);
- })
- .patch((req: Request, res: Response) => {
- res.status(200).send(`TODO: PATCH node info for ${req.params.nodeId}`);
- })
- .delete((req: Request, res: Response) => {
- res.status(200).send(`TODO: DELETE node info for ${req.params.nodeId}`);
- });
-
- return this.app;
- }
-}
diff --git a/src/service.ts b/src/service.ts
index 1f9eb41..579b21e 100644
--- a/src/service.ts
+++ b/src/service.ts
@@ -2,7 +2,6 @@ import debug from 'debug';
import {CommonRoutesConfig} from './common/common.routes.config';
import {UsersRoutes} from './users/users.routes.config';
import {FilesRoutes} from './files/files.routes.config';
-import {NodesRoutes} from './nodes/nodes.routes.config';
import express from 'express';
import * as http from 'http';
import {HyperledgerController} from './hyperledger/HyperledgerController';
@@ -24,7 +23,6 @@ app.use(cors());
routes.push(new UsersRoutes(app));
routes.push(new FilesRoutes(app));
-routes.push(new NodesRoutes(app));
const runningMessage = `Server running at http://localhost:${port}`;
app.get('/', (req: express.Request, res: express.Response) => {
diff --git a/src/users/testUsers.ts b/src/users/testUsers.ts
new file mode 100644
index 0000000..1236c7e
--- /dev/null
+++ b/src/users/testUsers.ts
@@ -0,0 +1,20 @@
+export const testUsers = [
+ {
+ address: 'asd123',
+ name: 'Jazmin',
+ availableSpace: 0,
+ usedSpace: 0,
+ },
+ {
+ address: 'qwe456',
+ name: 'Santi',
+ availableSpace: 25,
+ usedSpace: 10,
+ },
+ {
+ address: 'zxc789',
+ name: 'Fede',
+ availableSpace: 25,
+ usedSpace: 0,
+ },
+];
diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts
index a29386d..04a2482 100644
--- a/src/users/users.controller.ts
+++ b/src/users/users.controller.ts
@@ -1,4 +1,5 @@
import {HyperledgerController} from './../hyperledger/HyperledgerController';
+import {testUsers} from './testUsers';
//TODO improve error messages
export class UsersController {
diff --git a/src/users/users.routes.config.ts b/src/users/users.routes.config.ts
index ca9e6e2..40cc50c 100644
--- a/src/users/users.routes.config.ts
+++ b/src/users/users.routes.config.ts
@@ -1,6 +1,7 @@
import {CommonRoutesConfig} from '../common/common.routes.config';
import {Application, Request, Response, NextFunction} from 'express';
import {USERS} from '../common/common.routes.consts';
+import {authorized, getUserInfo} from '../common/authorization.service';
export class UsersRoutes extends CommonRoutesConfig {
constructor(app: Application) {
@@ -21,10 +22,24 @@ export class UsersRoutes extends CommonRoutesConfig {
.route(`/${USERS}/:userId`)
.all((req: Request, res: Response, next: NextFunction) => {
// Middleware executed on every route. @TODO: Validation @TODO: Authentication
+ if (!authorized(req.params.userId, req.headers.authorization)) {
+ console.debug(
+ '[Authorization] Failed authentication for user: ',
+ req.params.userId
+ );
+
+ res.status(403).send('Invalid credentials');
+ return;
+ }
+ console.debug(
+ '[Authorization] Authenticated user: ',
+ req.params.userId
+ );
+
next();
})
.get((req: Request, res: Response) => {
- res.status(200).send(`TODO: GET user info for ${req.params.userId}`);
+ res.status(200).send(getUserInfo(req.params.userId));
})
.patch((req: Request, res: Response) => {
res
diff --git a/wallet/wallet/admin.id b/wallet/wallet/admin.id
new file mode 100644
index 0000000..446f041
--- /dev/null
+++ b/wallet/wallet/admin.id
@@ -0,0 +1 @@
+{"credentials":{"certificate":"-----BEGIN CERTIFICATE-----\nMIIB8zCCAZmgAwIBAgIURzJBHOXU//r+5Gqifz98ahPVzzAwCgYIKoZIzj0EAwIw\ncDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMQ8wDQYDVQQH\nEwZEdXJoYW0xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh\nLm9yZzEuZXhhbXBsZS5jb20wHhcNMjIwODEwMjMwOTAwWhcNMjMwODEwMjMxNzAw\nWjAhMQ8wDQYDVQQLEwZjbGllbnQxDjAMBgNVBAMTBWFkbWluMFkwEwYHKoZIzj0C\nAQYIKoZIzj0DAQcDQgAEMHQJOWoxKwXdx76xzWI6wC+r+Zzx0EdxFX8TGCTNekYi\neOzLcIx1M9EsbKkUotJm15gaPr6biZVpj8QKuL48WaNgMF4wDgYDVR0PAQH/BAQD\nAgeAMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFKAUdu8PDLO6fKqHKp6d31Wtjt4t\nMB8GA1UdIwQYMBaAFIPR5D/XLQpbe2TWdTU3g3TiGvp6MAoGCCqGSM49BAMCA0gA\nMEUCIQD0V4BSCd/chpGnS2mmoMhktzgO4fDy7XYhes1xMKyDvwIgaL+KZQj0kP+F\nq0Bi29kbjDTBySzeRC1IYPBYTZMQP6c=\n-----END CERTIFICATE-----\n","privateKey":"-----BEGIN PRIVATE KEY-----\r\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg4C3thP8zVapM4xFV\r\nymNSj5CwTRIsP/rGiJD1bIz3LXahRANCAAQwdAk5ajErBd3HvrHNYjrAL6v5nPHQ\r\nR3EVfxMYJM16RiJ47MtwjHUz0SxsqRSi0mbXmBo+vpuJlWmPxAq4vjxZ\r\n-----END PRIVATE KEY-----\r\n"},"mspId":"Org1MSP","type":"X.509","version":1}
\ No newline at end of file
diff --git a/wallet/wallet/appUser.id b/wallet/wallet/appUser.id
new file mode 100644
index 0000000..a2f2210
--- /dev/null
+++ b/wallet/wallet/appUser.id
@@ -0,0 +1 @@
+{"credentials":{"certificate":"-----BEGIN CERTIFICATE-----\nMIIChDCCAiqgAwIBAgIUCrqo4udDJhYEpxyLDEJd/bSqgSwwCgYIKoZIzj0EAwIw\ncDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMQ8wDQYDVQQH\nEwZEdXJoYW0xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh\nLm9yZzEuZXhhbXBsZS5jb20wHhcNMjIwODEwMjMwOTAwWhcNMjMwODEwMjMxNzAw\nWjBEMTAwCwYDVQQLEwRvcmcxMA0GA1UECxMGY2xpZW50MBIGA1UECxMLZGVwYXJ0\nbWVudDExEDAOBgNVBAMTB2FwcFVzZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\nAATDE0yYvGlUb+EHxZcgTnNWUAyIN8O5ryxL71+uOdGeinifsNI7N3DrbV7lA/rd\nD7O28bbj6Z6Uj6QUjmiU/kk7o4HNMIHKMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMB\nAf8EAjAAMB0GA1UdDgQWBBSOAXUCGhPZTwloPbXu/o61LwunNzAfBgNVHSMEGDAW\ngBSD0eQ/1y0KW3tk1nU1N4N04hr6ejBqBggqAwQFBgcIAQReeyJhdHRycyI6eyJo\nZi5BZmZpbGlhdGlvbiI6Im9yZzEuZGVwYXJ0bWVudDEiLCJoZi5FbnJvbGxtZW50\nSUQiOiJhcHBVc2VyIiwiaGYuVHlwZSI6ImNsaWVudCJ9fTAKBggqhkjOPQQDAgNI\nADBFAiEAjLjw0Lm9CX8KpSn1FNmYURJ2ylfbzWedWcYIubn5QH4CIASbrdwffY4H\n1XIOw2c40CqlCkNEQPPsajyb6gtL2nTg\n-----END CERTIFICATE-----\n","privateKey":"-----BEGIN PRIVATE KEY-----\r\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg0CNIr4cG0hdixxZJ\r\nnQTL4HwrnBSsLpvqQAH4r3s8FrChRANCAATDE0yYvGlUb+EHxZcgTnNWUAyIN8O5\r\nryxL71+uOdGeinifsNI7N3DrbV7lA/rdD7O28bbj6Z6Uj6QUjmiU/kk7\r\n-----END PRIVATE KEY-----\r\n"},"mspId":"Org1MSP","type":"X.509","version":1}
\ No newline at end of file