Merge branch 'feature/anchoring-api' of github.com:smart-chain-fr/leCoffre into feature/anchoring-api
This commit is contained in:
commit
4a38b29ea3
15
package-lock.json
generated
15
package-lock.json
generated
@ -18,9 +18,11 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cron": "^2.3.1",
|
"cron": "^2.3.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"fp-ts": "^2.16.1",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.77",
|
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.77",
|
||||||
"module-alias": "^2.2.2",
|
"module-alias": "^2.2.2",
|
||||||
|
"monocle-ts": "^2.3.13",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"next": "^13.1.5",
|
"next": "^13.1.5",
|
||||||
"node-cache": "^5.1.2",
|
"node-cache": "^5.1.2",
|
||||||
@ -2474,6 +2476,11 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fp-ts": {
|
||||||
|
"version": "2.16.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.1.tgz",
|
||||||
|
"integrity": "sha512-by7U5W8dkIzcvDofUcO42yl9JbnHTEDBrzu3pt5fKT+Z4Oy85I21K80EYJYdjQGC2qum4Vo55Ag57iiIK4FYuA=="
|
||||||
|
},
|
||||||
"node_modules/fresh": {
|
"node_modules/fresh": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -3901,6 +3908,14 @@
|
|||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/monocle-ts": {
|
||||||
|
"version": "2.3.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/monocle-ts/-/monocle-ts-2.3.13.tgz",
|
||||||
|
"integrity": "sha512-D5Ygd3oulEoAm3KuGO0eeJIrhFf1jlQIoEVV2DYsZUMz42j4tGxgct97Aq68+F8w4w4geEnwFa8HayTS/7lpKQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"fp-ts": "^2.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
|
@ -51,9 +51,11 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cron": "^2.3.1",
|
"cron": "^2.3.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"fp-ts": "^2.16.1",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.77",
|
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.77",
|
||||||
"module-alias": "^2.2.2",
|
"module-alias": "^2.2.2",
|
||||||
|
"monocle-ts": "^2.3.13",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"next": "^13.1.5",
|
"next": "^13.1.5",
|
||||||
"node-cache": "^5.1.2",
|
"node-cache": "^5.1.2",
|
||||||
|
111
src/app/api/notary/OfficeFolderAnchorsController.ts
Normal file
111
src/app/api/notary/OfficeFolderAnchorsController.ts
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import { Response, Request } from "express";
|
||||||
|
import { Controller, Get, Post } from "@ControllerPattern/index";
|
||||||
|
import ApiController from "@Common/system/controller-pattern/ApiController";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||||
|
import { getFolderHashes } from "@Common/optics/notary";
|
||||||
|
import OfficeFoldersService from "@Services/notary/OfficeFoldersService/OfficeFoldersService";
|
||||||
|
import SecureService from "@Services/common/SecureService/SecureService";
|
||||||
|
import authHandler from "@App/middlewares/AuthHandler";
|
||||||
|
import ruleHandler from "@App/middlewares/RulesHandler";
|
||||||
|
import folderHandler from "@App/middlewares/OfficeMembershipHandlers/FolderHandler";
|
||||||
|
|
||||||
|
@Controller()
|
||||||
|
@Service()
|
||||||
|
export default class OfficeFoldersController extends ApiController {
|
||||||
|
constructor(private secureService: SecureService, private officeFoldersService: OfficeFoldersService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Create a new folder anchor
|
||||||
|
*/
|
||||||
|
@Post("/api/v1/notary/anchors/:uid", [authHandler, ruleHandler, folderHandler])
|
||||||
|
protected async post(req: Request, response: Response) {
|
||||||
|
try {
|
||||||
|
const uid = req.params["uid"];
|
||||||
|
|
||||||
|
if (!uid) {
|
||||||
|
this.httpBadRequest(response, "No uid provided");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
documents: {
|
||||||
|
include: {
|
||||||
|
files: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const officeFolderFound = await this.officeFoldersService.getByUid(uid, query);
|
||||||
|
|
||||||
|
if (!officeFolderFound) {
|
||||||
|
this.httpNotFoundRequest(response, "Office folder not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const officeFolder = OfficeFolder.hydrate<OfficeFolder>(officeFolderFound, { strategy: "excludeAll" });
|
||||||
|
const folderHashes = getFolderHashes(officeFolder);
|
||||||
|
|
||||||
|
if (folderHashes.length === 0) {
|
||||||
|
this.httpNotFoundRequest(response, "No file hash to anchor");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedHashes = [...folderHashes].sort();
|
||||||
|
const anchor = await this.secureService.anchor(sortedHashes);
|
||||||
|
|
||||||
|
this.httpSuccess(response, anchor);
|
||||||
|
} catch (error) {
|
||||||
|
this.httpInternalError(response, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Verify a folder anchor status
|
||||||
|
*/
|
||||||
|
@Get("/api/v1/notary/anchors/:uid", [authHandler, ruleHandler, folderHandler])
|
||||||
|
protected async get(req: Request, response: Response) {
|
||||||
|
try {
|
||||||
|
const uid = req.params["uid"];
|
||||||
|
|
||||||
|
if (!uid) {
|
||||||
|
this.httpBadRequest(response, "No uid provided");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
documents: {
|
||||||
|
include: {
|
||||||
|
files: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const officeFolderFound = await this.officeFoldersService.getByUid(uid, query);
|
||||||
|
|
||||||
|
if (!officeFolderFound) {
|
||||||
|
this.httpNotFoundRequest(response, "Office folder not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const officeFolder = OfficeFolder.hydrate<OfficeFolder>(officeFolderFound, { strategy: "excludeAll" });
|
||||||
|
const folderHashes = getFolderHashes(officeFolder);
|
||||||
|
|
||||||
|
if (folderHashes.length === 0) {
|
||||||
|
this.httpNotFoundRequest(response, "No file hash to anchor");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedHashes = [...folderHashes].sort();
|
||||||
|
const anchor = await this.secureService.verify(sortedHashes);
|
||||||
|
|
||||||
|
this.httpSuccess(response, anchor);
|
||||||
|
} catch (error) {
|
||||||
|
this.httpInternalError(response, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -41,6 +41,7 @@ import OfficeRolesControllerNotary from "./api/notary/OfficeRolesController";
|
|||||||
import FilesControllerCustomer from "./api/customer/FilesController";
|
import FilesControllerCustomer from "./api/customer/FilesController";
|
||||||
import DocumentsControllerCustomer from "./api/customer/DocumentsController";
|
import DocumentsControllerCustomer from "./api/customer/DocumentsController";
|
||||||
import OfficeFoldersController from "./api/customer/OfficeFoldersController";
|
import OfficeFoldersController from "./api/customer/OfficeFoldersController";
|
||||||
|
import OfficeFolderAnchorsController from "./api/notary/OfficeFolderAnchorsController";
|
||||||
import CustomersController from "./api/customer/CustomersController";
|
import CustomersController from "./api/customer/CustomersController";
|
||||||
import AppointmentsController from "./api/super-admin/AppointmentsController";
|
import AppointmentsController from "./api/super-admin/AppointmentsController";
|
||||||
import VotesController from "./api/super-admin/VotesController";
|
import VotesController from "./api/super-admin/VotesController";
|
||||||
@ -100,6 +101,7 @@ export default {
|
|||||||
Container.get(FilesControllerCustomer);
|
Container.get(FilesControllerCustomer);
|
||||||
Container.get(DocumentsControllerCustomer);
|
Container.get(DocumentsControllerCustomer);
|
||||||
Container.get(OfficeFoldersController);
|
Container.get(OfficeFoldersController);
|
||||||
|
Container.get(OfficeFolderAnchorsController);
|
||||||
Container.get(CustomersController)
|
Container.get(CustomersController)
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -76,11 +76,16 @@ export class BackendVariables {
|
|||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
public readonly MAILCHIMP_API_KEY!: string;
|
public readonly MAILCHIMP_API_KEY!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly SECURE_API_KEY!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly SECURE_API_BASE_URL!: string;
|
||||||
|
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
public readonly ENV!: string;
|
public readonly ENV!: string;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
this.DATABASE_PORT = process.env["DATABASE_PORT"]!;
|
this.DATABASE_PORT = process.env["DATABASE_PORT"]!;
|
||||||
this.DATABASE_HOST = process.env["DATABASE_HOST"]!;
|
this.DATABASE_HOST = process.env["DATABASE_HOST"]!;
|
||||||
@ -106,8 +111,9 @@ export class BackendVariables {
|
|||||||
this.ACCESS_TOKEN_SECRET = process.env["ACCESS_TOKEN_SECRET"]!;
|
this.ACCESS_TOKEN_SECRET = process.env["ACCESS_TOKEN_SECRET"]!;
|
||||||
this.REFRESH_TOKEN_SECRET = process.env["REFRESH_TOKEN_SECRET"]!;
|
this.REFRESH_TOKEN_SECRET = process.env["REFRESH_TOKEN_SECRET"]!;
|
||||||
this.MAILCHIMP_API_KEY = process.env["MAILCHIMP_API_KEY"]!;
|
this.MAILCHIMP_API_KEY = process.env["MAILCHIMP_API_KEY"]!;
|
||||||
|
this.SECURE_API_KEY = process.env["SECURE_API_KEY"]!;
|
||||||
|
this.SECURE_API_BASE_URL = process.env["SECURE_API_BASE_URL"]!;
|
||||||
this.ENV = process.env["ENV"]!;
|
this.ENV = process.env["ENV"]!;
|
||||||
|
|
||||||
}
|
}
|
||||||
public async validate(groups?: string[]) {
|
public async validate(groups?: string[]) {
|
||||||
const validationOptions = groups ? { groups } : undefined;
|
const validationOptions = groups ? { groups } : undefined;
|
||||||
|
29
src/common/optics/notary/index.ts
Normal file
29
src/common/optics/notary/index.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import * as Optics from "monocle-ts";
|
||||||
|
import * as Traversal from "monocle-ts/Traversal";
|
||||||
|
import * as Array from "fp-ts/Array";
|
||||||
|
|
||||||
|
import { Document, File, OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lenses
|
||||||
|
*/
|
||||||
|
export const folderDocumentsLens = Optics.Lens.fromNullableProp<OfficeFolder>()("documents", []);
|
||||||
|
export const documentFilesLens = Optics.Lens.fromNullableProp<Document>()("files", []);
|
||||||
|
export const fileHashLens = Optics.Lens.fromProp<File>()("hash");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traversals
|
||||||
|
*/
|
||||||
|
export const documentsTraversal = Optics.fromTraversable(Array.Traversable)<Document>();
|
||||||
|
export const filesTraversal = Optics.fromTraversable(Array.Traversable)<File>();
|
||||||
|
|
||||||
|
export const folderHashesTraversal = folderDocumentsLens
|
||||||
|
.composeTraversal(documentsTraversal)
|
||||||
|
.composeLens(documentFilesLens)
|
||||||
|
.composeTraversal(filesTraversal)
|
||||||
|
.composeLens(fileHashLens);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getters
|
||||||
|
*/
|
||||||
|
export const getFolderHashes = (folder: OfficeFolder) => Traversal.getAll(folder)(folderHashesTraversal);
|
59
src/services/common/SecureService/SecureService.ts
Normal file
59
src/services/common/SecureService/SecureService.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import BaseService from "@Services/BaseService";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
import { BackendVariables } from "@Common/config/variables/Variables";
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class SecureService extends BaseService {
|
||||||
|
constructor(protected variables: BackendVariables) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : anchor a sequence of hashes
|
||||||
|
* @throws {Error} If secure job cannot be created
|
||||||
|
*/
|
||||||
|
public async anchor(hash_sources: string[]) {
|
||||||
|
const url = new URL(this.variables.SECURE_API_BASE_URL.concat("/flows/v2/anchor"));
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
apiKey: this.variables.SECURE_API_KEY,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
hash_sources,
|
||||||
|
callback_url: "",
|
||||||
|
callback_config: {},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : verify if a sequence of hashes is anchored
|
||||||
|
* @throws {Error} If secure job cannot be found
|
||||||
|
*/
|
||||||
|
public async verify(hash_sources: string[]) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
hash_sources.forEach((hash) => {
|
||||||
|
params.append("hash_sources", hash);
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = new URL(this.variables.SECURE_API_BASE_URL.concat("/flows/v2/verify?").concat(params.toString()));
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
apiKey: this.variables.SECURE_API_KEY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user