From 337428c36eb9e51db4cbf6c703966805f8ab8cf6 Mon Sep 17 00:00:00 2001 From: Federico Parodi Date: Thu, 27 Oct 2022 17:31:16 -0300 Subject: [PATCH 01/10] feat: mock authorization service --- .gitignore | 2 ++ src/common/authorization.service.ts | 14 ++++++++++++++ src/files/files.routes.config.ts | 15 ++++++++++++--- src/users/testUsers.ts | 20 ++++++++++++++++++++ src/users/users.controller.ts | 5 +++++ src/users/users.routes.config.ts | 15 +++++++++++++++ 6 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 src/common/authorization.service.ts create mode 100644 src/users/testUsers.ts 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/src/common/authorization.service.ts b/src/common/authorization.service.ts new file mode 100644 index 0000000..a0609bb --- /dev/null +++ b/src/common/authorization.service.ts @@ -0,0 +1,14 @@ +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 + ); +}; diff --git a/src/files/files.routes.config.ts b/src/files/files.routes.config.ts index 7a63c4a..2c1a342 100644 --- a/src/files/files.routes.config.ts +++ b/src/files/files.routes.config.ts @@ -1,5 +1,6 @@ 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'; @@ -14,12 +15,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) => 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..3b4d0a8 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 { @@ -28,4 +29,8 @@ export class UsersController { ) return; //TODO Return some error or sth } + + public async getUserInfo(walletAddress: string) { + return testUsers.find(user => user.address === walletAddress); + } } diff --git a/src/users/users.routes.config.ts b/src/users/users.routes.config.ts index ca9e6e2..a34adfd 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} from '../common/authorization.service'; export class UsersRoutes extends CommonRoutesConfig { constructor(app: Application) { @@ -21,6 +22,20 @@ 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) => { From 4ddaf1a8618b992af3853f8fc04c8ff0281da769 Mon Sep 17 00:00:00 2001 From: Federico Parodi Date: Thu, 27 Oct 2022 19:41:26 -0300 Subject: [PATCH 02/10] feat: get users route --- src/common/authorization.service.ts | 4 ++++ src/users/users.controller.ts | 4 ---- src/users/users.routes.config.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/authorization.service.ts b/src/common/authorization.service.ts index a0609bb..b5c4459 100644 --- a/src/common/authorization.service.ts +++ b/src/common/authorization.service.ts @@ -12,3 +12,7 @@ export const authorized = (userId: string, authToken: string = '') => { userId == authToken ); }; + +export const getUserInfo = (walletAddress: string) => { + return testUsers.find(user => user.address === walletAddress); +}; diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 3b4d0a8..04a2482 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -29,8 +29,4 @@ export class UsersController { ) return; //TODO Return some error or sth } - - public async getUserInfo(walletAddress: string) { - return testUsers.find(user => user.address === walletAddress); - } } diff --git a/src/users/users.routes.config.ts b/src/users/users.routes.config.ts index a34adfd..40cc50c 100644 --- a/src/users/users.routes.config.ts +++ b/src/users/users.routes.config.ts @@ -1,7 +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} from '../common/authorization.service'; +import {authorized, getUserInfo} from '../common/authorization.service'; export class UsersRoutes extends CommonRoutesConfig { constructor(app: Application) { @@ -39,7 +39,7 @@ export class UsersRoutes extends CommonRoutesConfig { 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 From dae8dea70cbf2dd7b81ea442ddef3d1ad8384867 Mon Sep 17 00:00:00 2001 From: Federico Parodi Date: Sat, 29 Oct 2022 19:25:48 -0300 Subject: [PATCH 03/10] fix: some return statements --- src/files/files.routes.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/files/files.routes.config.ts b/src/files/files.routes.config.ts index 2c1a342..aafae18 100644 --- a/src/files/files.routes.config.ts +++ b/src/files/files.routes.config.ts @@ -44,7 +44,7 @@ export class FilesRoutes extends CommonRoutesConfig { if (!file) res.status(400).send('Missing file'); - filesController + return filesController .createFile(req.params.userId, file) .then(result => res.status(201).send(result)) .catch(error => @@ -94,7 +94,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 => From 7d88a505dbe02786dc397ef2a593de2a0515a575 Mon Sep 17 00:00:00 2001 From: Federico Parodi Date: Sat, 29 Oct 2022 19:28:17 -0300 Subject: [PATCH 04/10] fix: another missing return --- src/files/files.routes.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/files/files.routes.config.ts b/src/files/files.routes.config.ts index aafae18..b73e0a5 100644 --- a/src/files/files.routes.config.ts +++ b/src/files/files.routes.config.ts @@ -42,7 +42,7 @@ export class FilesRoutes extends CommonRoutesConfig { .post((req: Request, res: Response) => { const file = req.files?.file; - if (!file) res.status(400).send('Missing file'); + if (!file) return res.status(400).send('Missing file'); return filesController .createFile(req.params.userId, file) From a82cfc60ea5c7ff472846fa908221336eeb82d67 Mon Sep 17 00:00:00 2001 From: bizk Date: Sun, 30 Oct 2022 11:50:00 -0300 Subject: [PATCH 05/10] small improvements --- src/hyperledger/StorageOperationController.ts | 2 +- src/ipfs/ipfs.service.ts | 8 ++++---- wallet/wallet/admin.id | 1 + wallet/wallet/appUser.id | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 wallet/wallet/admin.id create mode 100644 wallet/wallet/appUser.id 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..3ee89d3 100644 --- a/src/ipfs/ipfs.service.ts +++ b/src/ipfs/ipfs.service.ts @@ -3,9 +3,9 @@ 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 - */ + +const IPFS_DEFAULT_URL = 'http://127.0.0.1:5001/api/v0'; + class IPFSService { private static ipfsHttpClient: IPFSHTTPClient; /** @@ -17,7 +17,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, }); } 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 From a723ec035cdfb9551714e9c9a0ef2539a2e34ef5 Mon Sep 17 00:00:00 2001 From: bizk Date: Sun, 30 Oct 2022 11:51:10 -0300 Subject: [PATCH 06/10] removed nodes api --- src/nodes/nodes.routes.config.ts | 41 -------------------------------- src/service.ts | 2 -- 2 files changed, 43 deletions(-) delete mode 100644 src/nodes/nodes.routes.config.ts 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) => { From 5104f0b11a75306956059801fd35d73258adb27f Mon Sep 17 00:00:00 2001 From: Federico Parodi Date: Sun, 30 Oct 2022 18:26:02 -0300 Subject: [PATCH 07/10] feat: handle encryption --- src/files/files.controller.ts | 3 ++- src/files/files.routes.config.ts | 12 +++++++++-- .../interfaces/IEncryptedFile.interface.ts | 6 ++++++ src/ipfs/ipfs.service.ts | 21 +++++++++++-------- 4 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 src/files/interfaces/IEncryptedFile.interface.ts 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 b73e0a5..f247694 100644 --- a/src/files/files.routes.config.ts +++ b/src/files/files.routes.config.ts @@ -4,6 +4,7 @@ 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) { @@ -40,9 +41,16 @@ export class FilesRoutes extends CommonRoutesConfig { ) ) .post((req: Request, res: Response) => { - const file = req.files?.file; + const file: IEncryptedFile = {...req.body}; - if (!file) return res.status(400).send('Missing file'); + if ( + !file || + !file.address || + !file.fileData || + !file.name || + !file.type + ) + return res.status(400).send('Missing file'); return filesController .createFile(req.params.userId, file) 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/ipfs/ipfs.service.ts b/src/ipfs/ipfs.service.ts index 3ee89d3..bb74af8 100644 --- a/src/ipfs/ipfs.service.ts +++ b/src/ipfs/ipfs.service.ts @@ -3,8 +3,9 @@ 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'; +import {IEncryptedFile} from '../files/interfaces/IEncryptedFile.interface'; -const IPFS_DEFAULT_URL = 'http://127.0.0.1:5001/api/v0'; +const IPFS_DEFAULT_URL = 'http://127.0.0.1:5002/api/v0'; class IPFSService { private static ipfsHttpClient: IPFSHTTPClient; @@ -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 From f2a940b8c084608a5628f78ae624d40db7d0ae6e Mon Sep 17 00:00:00 2001 From: bizk Date: Sun, 30 Oct 2022 18:38:09 -0300 Subject: [PATCH 08/10] hyperledger fix --- src/hyperledger/HyperledgerController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 43a01bd8a48d54bbd11dd116620286316b82b233 Mon Sep 17 00:00:00 2001 From: bizk Date: Sun, 30 Oct 2022 22:43:04 -0300 Subject: [PATCH 09/10] small modification on default url --- src/ipfs/ipfs.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipfs/ipfs.service.ts b/src/ipfs/ipfs.service.ts index bb74af8..2dc3ebf 100644 --- a/src/ipfs/ipfs.service.ts +++ b/src/ipfs/ipfs.service.ts @@ -5,7 +5,7 @@ import all from 'it-all'; import {KFSEntry} from '../files/files.interfaces'; import {IEncryptedFile} from '../files/interfaces/IEncryptedFile.interface'; -const IPFS_DEFAULT_URL = 'http://127.0.0.1:5002/api/v0'; +const IPFS_DEFAULT_URL = 'http://127.0.0.1:5001/api/v0'; class IPFSService { private static ipfsHttpClient: IPFSHTTPClient; From 05ffac88f5b8d3bcb0107ba0b8177e39a65d5a7f Mon Sep 17 00:00:00 2001 From: bizk Date: Sun, 30 Oct 2022 22:56:11 -0300 Subject: [PATCH 10/10] added readme --- README.md | 139 +++++++++++++++++++++++++++++++++---- resources/Kintoisologo.png | Bin 0 -> 14082 bytes 2 files changed, 126 insertions(+), 13 deletions(-) create mode 100644 resources/Kintoisologo.png 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 + logo +

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. +

+ +

+ Documentation + · + Report Bug + · + Request Feature +

+
-### 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 0000000000000000000000000000000000000000..83cb207584596638ecffa53d2e192aeb0560a224 GIT binary patch literal 14082 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