From dddb5cb07b3ab15b423046a26c578597b5275019 Mon Sep 17 00:00:00 2001 From: Fabian Stamm Date: Sun, 23 Dec 2018 15:18:30 +0100 Subject: [PATCH 1/2] Wrapper for new Secure File remake --- .gitignore | 1 + .npmignore | 3 + entry.js | 0 index.d.ts | 60 ------- index.js | 408 ---------------------------------------------- index.ts | 241 --------------------------- package-lock.json | 292 ++++++++++++++++++++++++++++++++- package.json | 22 +-- public.pem | 9 - public.pem.b64 | 1 - src/index.ts | 223 +++++++++++++++++++++++++ src/lock.ts | 36 ++++ src/observable.ts | 38 +++++ src/test.ts | 130 +++++++++++++++ test.d.ts | 1 - test.js | 230 -------------------------- test.ts | 129 --------------- tsconfig.json | 51 +----- 18 files changed, 733 insertions(+), 1142 deletions(-) create mode 100644 .npmignore delete mode 100644 entry.js delete mode 100644 index.d.ts delete mode 100644 index.js delete mode 100644 index.ts delete mode 100644 public.pem delete mode 100644 public.pem.b64 create mode 100644 src/index.ts create mode 100644 src/lock.ts create mode 100644 src/observable.ts create mode 100644 src/test.ts delete mode 100644 test.d.ts delete mode 100644 test.js delete mode 100644 test.ts diff --git a/.gitignore b/.gitignore index 18c3786..0d6b477 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ yarn.lock private.pem +lib/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..fa86359 --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +tsconfig.json +src/ +node_modules/ diff --git a/entry.js b/entry.js deleted file mode 100644 index e69de29..0000000 diff --git a/index.d.ts b/index.d.ts deleted file mode 100644 index 8a13fff..0000000 --- a/index.d.ts +++ /dev/null @@ -1,60 +0,0 @@ -/// -import "isomorphic-fetch"; -export interface File { - _id: string; - type: "binary" | "text"; - name: string; - time: string; - preview: string; - version: string; - meta: string; -} -export interface History { - file: File; - history: File[]; -} -export default class SecureFile { - private Server; - private Username; - private PrivateKey; - private jwt_enabled; - private JWT; - constructor(server: string, username: string, private_key: string, jwt?: boolean); - private getCode(); - private getJWT(); - makeRequest(endpoint: string, method: "POST" | "GET" | "PUT" | "DELETE", query: any, body?: Buffer, jwt?: boolean): any; - test(): Promise<{ - user: string; - test: true; - }>; - list(folder?: string): Promise; - create(name: string, data: Buffer, type: "text" | "binary", folder?: string, preview?: Buffer, meta?: string): Promise; - get(id: string, version?: string): Promise; - update(id: string, data: Buffer, preview?: Buffer, meta?: string): Promise; - delete(id: string): Promise; - history(id: string): Promise; -} -export declare class NoConnection extends Error { - type: string; - constructor(); -} -export declare class Unauthorized extends Error { - type: string; - constructor(); -} -export declare class NoPermission extends Error { - type: string; - constructor(); -} -export declare class InvalidJWT extends Error { - type: string; - constructor(); -} -export declare class NotFound extends Error { - type: string; - constructor(); -} -export declare class BadRequest extends Error { - type: string; - constructor(); -} diff --git a/index.js b/index.js deleted file mode 100644 index 6a0d9df..0000000 --- a/index.js +++ /dev/null @@ -1,408 +0,0 @@ -"use strict"; -var __extends = (this && this.__extends) || (function () { - var extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [0, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -Object.defineProperty(exports, "__esModule", { value: true }); -var rsa = require("node-rsa"); -require("isomorphic-fetch"); -var btb = require("blob-to-buffer"); -var SecureFile = /** @class */ (function () { - function SecureFile(server, username, private_key, jwt) { - if (jwt === void 0) { jwt = false; } - this.Server = server; - if (this.Server.endsWith("/")) { - this.Server += "api"; - } - else { - this.Server += "/api"; - } - this.Username = username; - this.PrivateKey = private_key; - this.jwt_enabled = jwt; - } - SecureFile.prototype.getCode = function () { - return __awaiter(this, void 0, void 0, function () { - var myHeaders, myInit, code_res, err_1, code, r; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - myHeaders = new Headers(); - myHeaders.append('pragma', 'no-cache'); - myHeaders.append('cache-control', 'no-cache'); - myInit = { - method: 'GET', - headers: myHeaders, - }; - _a.label = 1; - case 1: - _a.trys.push([1, 3, , 4]); - return [4 /*yield*/, fetch(this.Server + "/code?username=" + this.Username, myInit)]; - case 2: - code_res = _a.sent(); - return [3 /*break*/, 4]; - case 3: - err_1 = _a.sent(); - //TODO probably better fail check - throw new NoConnection(); - case 4: - if (code_res.status == 403) - throw new Error("Unauthorized"); - statusParser(code_res); - return [4 /*yield*/, code_res.json()]; - case 5: - code = (_a.sent()).code; - r = new rsa(this.PrivateKey, "pkcs1-pem"); - return [2 /*return*/, { code: code, signature: r.sign(code).toString("base64") }]; - } - }); - }); - }; - SecureFile.prototype.getJWT = function () { - return __awaiter(this, void 0, void 0, function () { - var res, jwt; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, this.makeRequest("/jwt", "GET", {}, undefined, false)]; - case 1: - res = _a.sent(); - statusParser(res); - return [4 /*yield*/, res.text()]; - case 2: - jwt = _a.sent(); - this.JWT = jwt; - return [2 /*return*/]; - } - }); - }); - }; - SecureFile.prototype.makeRequest = function (endpoint, method, query, body, jwt) { - if (jwt === void 0) { jwt = false; } - return __awaiter(this, void 0, void 0, function () { - var code, query_str, first, key, myHeaders, res, err_2; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - if (!(this.jwt_enabled && jwt)) return [3 /*break*/, 3]; - if (!!this.JWT) return [3 /*break*/, 2]; - return [4 /*yield*/, this.getJWT()]; - case 1: - _a.sent(); - _a.label = 2; - case 2: - query.jwt = this.JWT; - return [3 /*break*/, 5]; - case 3: return [4 /*yield*/, this.getCode()]; - case 4: - code = _a.sent(); - query.code = code.code; - query.signature = code.signature; - _a.label = 5; - case 5: - query_str = "?"; - first = true; - for (key in query) { - if (!first) - query_str += "&"; - query_str += encodeURIComponent(key) + "=" + encodeURIComponent(query[key]); - first = false; - } - myHeaders = new Headers(); - myHeaders.append('pragma', 'no-cache'); - myHeaders.append('cache-control', 'no-cache'); - _a.label = 6; - case 6: - _a.trys.push([6, 8, , 9]); - return [4 /*yield*/, fetch(this.Server + endpoint + query_str, { method: method, body: body, headers: myHeaders })]; - case 7: - res = _a.sent(); - if (res.status === 418) { - this.JWT = undefined; - return [2 /*return*/, this.makeRequest(endpoint, method, query, body, jwt)]; - } - return [2 /*return*/, res]; - case 8: - err_2 = _a.sent(); - if (err_2 instanceof TypeError || err_2.errno === "ECONNREFUSED") - throw new NoConnection(); - console.log(err_2); - throw err_2; - case 9: return [2 /*return*/]; - } - }); - }); - }; - SecureFile.prototype.test = function () { - return __awaiter(this, void 0, void 0, function () { - var res; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, this.makeRequest("/test", "GET", {}, undefined, this.jwt_enabled)]; - case 1: - res = _a.sent(); - statusParser(res); - return [4 /*yield*/, res.json()]; - case 2: return [2 /*return*/, _a.sent()]; - } - }); - }); - }; - SecureFile.prototype.list = function (folder) { - return __awaiter(this, void 0, void 0, function () { - var res; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - if (!folder) - folder = "root"; - return [4 /*yield*/, this.makeRequest("/files", "GET", { folder: folder }, undefined, this.jwt_enabled)]; - case 1: - res = _a.sent(); - statusParser(res); - return [4 /*yield*/, res.json()]; - case 2: return [2 /*return*/, _a.sent()]; - } - }); - }); - }; - SecureFile.prototype.create = function (name, data, type, folder, preview, meta) { - return __awaiter(this, void 0, void 0, function () { - var params, res; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - params = { type: type, name: name }; - if (preview) { - params.preview = preview.toString("base64"); - } - if (folder) { - params.folder = folder; - } - if (meta) { - params.meta = meta; - } - return [4 /*yield*/, this.makeRequest("/files", "POST", params, data, this.jwt_enabled)]; - case 1: - res = _a.sent(); - statusParser(res); - return [2 /*return*/, res.json()]; - } - }); - }); - }; - SecureFile.prototype.get = function (id, version) { - return __awaiter(this, void 0, void 0, function () { - var _this = this; - var res; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - if (!(typeof version === "string")) return [3 /*break*/, 2]; - return [4 /*yield*/, this.makeRequest("/files/" + id + "/history/" + version, "GET", {}, undefined, this.jwt_enabled)]; - case 1: - res = _a.sent(); - return [3 /*break*/, 4]; - case 2: return [4 /*yield*/, this.makeRequest("/files/" + id, "GET", {}, undefined, this.jwt_enabled)]; - case 3: - res = _a.sent(); - _a.label = 4; - case 4: - statusParser(res); - if (res.buffer) { - return [2 /*return*/, res.buffer()]; - } - else { - return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () { - var _a; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - _a = btb; - return [4 /*yield*/, res.blob()]; - case 1: - _a.apply(void 0, [_b.sent(), function (err, buffer) { - if (err) - reject(err); - else - resolve(buffer); - }]); - return [2 /*return*/]; - } - }); - }); })]; - } - return [2 /*return*/]; - } - }); - }); - }; - SecureFile.prototype.update = function (id, data, preview, meta) { - return __awaiter(this, void 0, void 0, function () { - var put, res; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - put = {}; - if (preview) - put.preview = preview.toString("base64"); - if (meta) - put.meta = meta; - return [4 /*yield*/, this.makeRequest("/files/" + id, "PUT", put, data, this.jwt_enabled)]; - case 1: - res = _a.sent(); - statusParser(res); - return [2 /*return*/, res.json()]; - } - }); - }); - }; - SecureFile.prototype.delete = function (id) { - return __awaiter(this, void 0, void 0, function () { - var res; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, this.makeRequest("/files/" + id, "DELETE", {}, undefined, this.jwt_enabled)]; - case 1: - res = _a.sent(); - statusParser(res); - return [2 /*return*/, res.json()]; - } - }); - }); - }; - SecureFile.prototype.history = function (id) { - return __awaiter(this, void 0, void 0, function () { - var res; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, this.makeRequest("/files/" + id + "/history", "GET", {}, undefined, this.jwt_enabled)]; - case 1: - res = _a.sent(); - statusParser(res); - return [2 /*return*/, res.json()]; - } - }); - }); - }; - return SecureFile; -}()); -exports.default = SecureFile; -var NoConnection = /** @class */ (function (_super) { - __extends(NoConnection, _super); - function NoConnection() { - var _this = _super.call(this, "No connection") || this; - _this.type = "noconnection"; - return _this; - } - return NoConnection; -}(Error)); -exports.NoConnection = NoConnection; -var Unauthorized = /** @class */ (function (_super) { - __extends(Unauthorized, _super); - function Unauthorized() { - var _this = _super.call(this, "Not authorized") || this; - _this.type = "unauthorized"; - return _this; - } - return Unauthorized; -}(Error)); -exports.Unauthorized = Unauthorized; -var NoPermission = /** @class */ (function (_super) { - __extends(NoPermission, _super); - function NoPermission() { - var _this = _super.call(this, "No permission") || this; - _this.type = "nopermission"; - return _this; - } - return NoPermission; -}(Error)); -exports.NoPermission = NoPermission; -var InvalidJWT = /** @class */ (function (_super) { - __extends(InvalidJWT, _super); - function InvalidJWT() { - var _this = _super.call(this, "Invalid JWT") || this; - _this.type = "invalidjwt"; - return _this; - } - return InvalidJWT; -}(Error)); -exports.InvalidJWT = InvalidJWT; -var NotFound = /** @class */ (function (_super) { - __extends(NotFound, _super); - function NotFound() { - var _this = _super.call(this, "Not found") || this; - _this.type = "notfound"; - return _this; - } - return NotFound; -}(Error)); -exports.NotFound = NotFound; -var BadRequest = /** @class */ (function (_super) { - __extends(BadRequest, _super); - function BadRequest() { - var _this = _super.call(this, "Bad request") || this; - _this.type = "badrequest"; - return _this; - } - return BadRequest; -}(Error)); -exports.BadRequest = BadRequest; -function statusParser(res) { - if (res.status !== 200) { - switch (res.status) { - case 400: - throw new BadRequest(); - case 404: - throw new NotFound(); - case 403: - throw new NoPermission(); - case 401: - throw new Unauthorized(); - case 418: - throw new InvalidJWT(); - default: - throw new Error(res.statusText); - } - } -} diff --git a/index.ts b/index.ts deleted file mode 100644 index fd9efad..0000000 --- a/index.ts +++ /dev/null @@ -1,241 +0,0 @@ -import * as rsa from "node-rsa"; -import "isomorphic-fetch"; -import * as btb from "blob-to-buffer" - - -export interface File { - _id: string; - type: "binary" | "text"; - name: string; - time: string; - preview: string; - version: string; - meta: string; -} - -export interface History { - file: File; - history: File[]; -} - -export default class SecureFile { - private Server: string; - private Username: string; - private PrivateKey: string; - private jwt_enabled: boolean; - private JWT: string; - - constructor(server: string, username: string, private_key: string, jwt = false) { - this.Server = server; - if (this.Server.endsWith("/")) { - this.Server += "api"; - } else { - this.Server += "/api"; - } - this.Username = username; - this.PrivateKey = private_key; - this.jwt_enabled = jwt; - } - - private async getCode() { - var myHeaders = new Headers(); - myHeaders.append('pragma', 'no-cache'); - myHeaders.append('cache-control', 'no-cache'); - - var myInit = { - method: 'GET', - headers: myHeaders, - }; - try { - var code_res = await fetch(this.Server + "/code?username=" + this.Username, myInit); - } catch (err) { - //TODO probably better fail check - throw new NoConnection(); - } - if (code_res.status == 403) throw new Error("Unauthorized"); - statusParser(code_res); - let code = (await code_res.json()).code; - let r = new rsa(this.PrivateKey, "pkcs1-pem"); - return { code: code, signature: r.sign(code).toString("base64") }; - } - - private async getJWT() { - let res = await this.makeRequest("/jwt", "GET", {}, undefined, false); - statusParser(res); - let jwt = await res.text(); - this.JWT = jwt; - } - - public async makeRequest(endpoint: string, method: "POST" | "GET" | "PUT" | "DELETE", query: any, body?: Buffer, jwt = false) { - if (this.jwt_enabled && jwt) { - if (!this.JWT) await this.getJWT(); - query.jwt = this.JWT; - } else { - let code = await this.getCode(); - query.code = code.code; - query.signature = code.signature; - } - - let query_str = "?"; - let first = true; - for (let key in query) { - if (!first) query_str += "&"; - query_str += encodeURIComponent(key) + "=" + encodeURIComponent(query[key]); - first = false; - } - var myHeaders = new Headers(); - myHeaders.append('pragma', 'no-cache'); - myHeaders.append('cache-control', 'no-cache'); - try { - let res = await fetch(this.Server + endpoint + query_str, { method: method, body: body, headers: myHeaders }); - if (res.status === 418) { // JWT invalid - this.JWT = undefined; - return this.makeRequest(endpoint, method, query, body, jwt); - } - return res; - } catch (err) { - if (err instanceof TypeError || err.errno === "ECONNREFUSED") - throw new NoConnection(); - console.log(err); - throw err; - } - } - - async test(): Promise<{ user: string, test: true }> { - let res = await this.makeRequest("/test", "GET", {}, undefined, this.jwt_enabled); - statusParser(res); - return await res.json(); - } - - async list(folder?: string): Promise { - if (!folder) folder = "root"; - let res = await this.makeRequest("/files", "GET", { folder: folder }, undefined, this.jwt_enabled) - statusParser(res); - return await res.json(); - } - - async create(name: string, data: Buffer, type: "text" | "binary", folder?: string, preview?: Buffer, meta?: string): Promise { - let params: any = { type: type, name: name }; - if (preview) { - params.preview = preview.toString("base64"); - } - if (folder) { - params.folder = folder; - } - - if (meta) { - params.meta = meta; - } - let res = await this.makeRequest("/files", "POST", params, data, this.jwt_enabled) - statusParser(res); - return res.json(); - } - - async get(id: string, version?: string): Promise { - let res: Response; - if (typeof version === "string") { - res = await this.makeRequest(`/files/${id}/history/${version}`, "GET", {}, undefined, this.jwt_enabled); - } else { - res = await this.makeRequest("/files/" + id, "GET", {}, undefined, this.jwt_enabled); - } - statusParser(res); - if ((res).buffer) { - return (res).buffer(); - } else { - return new Promise(async (resolve, reject) => { - btb(await res.blob(), (err, buffer) => { - if (err) reject(err); - else resolve(buffer); - }) - }) - } - } - - async update(id: string, data: Buffer, preview?: Buffer, meta?: string): Promise { - let put: any = {}; - if (preview) put.preview = preview.toString("base64"); - if (meta) put.meta = meta; - let res = await this.makeRequest("/files/" + id, "PUT", put, data, this.jwt_enabled); - statusParser(res); - return res.json(); - } - - async delete(id: string): Promise { - let res = await this.makeRequest("/files/" + id, "DELETE", {}, undefined, this.jwt_enabled); - statusParser(res); - return res.json(); - } - - async history(id: string): Promise { - let res = await this.makeRequest(`/files/${id}/history`, "GET", {}, undefined, this.jwt_enabled); - statusParser(res); - return res.json(); - } -} - -export class NoConnection extends Error { - type: string; - constructor() { - super("No connection"); - this.type = "noconnection" - } -} - -export class Unauthorized extends Error { - type: string; - constructor() { - super("Not authorized"); - this.type = "unauthorized" - } -} - -export class NoPermission extends Error { - type: string; - constructor() { - super("No permission"); - this.type = "nopermission" - } -} - -export class InvalidJWT extends Error { - type: string; - constructor() { - super("Invalid JWT"); - this.type = "invalidjwt" - } -} - -export class NotFound extends Error { - type: string; - constructor() { - super("Not found"); - this.type = "notfound" - } -} - -export class BadRequest extends Error { - type: string; - constructor() { - super("Bad request"); - this.type = "badrequest" - } -} - -function statusParser(res: Response) { - if (res.status !== 200) { - switch (res.status) { - case 400: - throw new BadRequest(); - case 404: - throw new NotFound(); - case 403: - throw new NoPermission(); - case 401: - throw new Unauthorized(); - case 418: - throw new InvalidJWT(); - default: - throw new Error(res.statusText); - } - } -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 235da2f..10f459a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,205 @@ { "name": "secure-file-wrapper", - "version": "1.0.2", + "version": "1.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/chai": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", + "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==", + "dev": true + }, + "@types/isomorphic-fetch": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.34.tgz", + "integrity": "sha1-PDSD5gbAQTeEOOlRRk8A5OYHBtY=", + "dev": true + }, + "@types/mocha": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.5.tgz", + "integrity": "sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww==", + "dev": true + }, + "@types/node": { + "version": "10.12.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", + "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==", + "dev": true + }, + "@types/node-fetch": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.1.4.tgz", + "integrity": "sha512-tR1ekaXUGpmzOcDXWU9BW73YfA2/VW1DF1FH+wlJ82BbCSnWTbdX+JkqWQXWKIGsFPnPsYadbXfNgz28g+ccWg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "commander": { + "version": "2.15.1", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, "encoding": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "requires": { - "iconv-lite": "0.4.19" + "iconv-lite": "~0.4.13" } }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, "iconv-lite": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -27,8 +210,8 @@ "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", "requires": { - "node-fetch": "1.7.3", - "whatwg-fetch": "2.0.3" + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" }, "dependencies": { "node-fetch": { @@ -36,16 +219,113 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", "requires": { - "encoding": "0.1.12", - "is-stream": "1.1.0" + "encoding": "^0.1.11", + "is-stream": "^1.0.1" } } } }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "typescript": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", + "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", + "dev": true + }, "whatwg-fetch": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true } } } diff --git a/package.json b/package.json index f488115..eda2628 100644 --- a/package.json +++ b/package.json @@ -1,31 +1,27 @@ { "name": "secure-file-wrapper", - "version": "1.1.0", - "main": "index.js", + "version": "2.0.0", + "main": "lib/index.js", "author": "Fabian Stamm ", "license": "MIT", - "types": "index.d.ts", + "types": "lib/index.d.ts", "scripts": { "build": "tsc", "watch": "tsc --watch", - "test": "mocha test.js" + "prepublish": "tsc", + "test": "mocha lib/test.js" }, "dependencies": { - "blob-to-buffer": "^1.2.7", - "isomorphic-fetch": "^2.2.1", - "node-rsa": "^0.4.2" + "isomorphic-fetch": "^2.2.1" }, "devDependencies": { - "@types/blob-to-buffer": "^1.2.0", "@types/chai": "^4.1.4", "@types/isomorphic-fetch": "^0.0.34", "@types/mocha": "^5.2.2", - "@types/node": "^9.4.0", - "@types/node-fetch": "^1.6.7", - "@types/node-rsa": "^0.4.1", + "@types/node": "^10.12.18", + "@types/node-fetch": "^2.1.4", "chai": "^4.1.2", "mocha": "^5.2.0", - "nodeunit": "^0.11.2", - "typescript": "^2.6.2" + "typescript": "^3.2.2" } } \ No newline at end of file diff --git a/public.pem b/public.pem deleted file mode 100644 index 4d08d8b..0000000 --- a/public.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkekKOaeZtnCHr8L8msgg -J0vesw4JbJl21YtEH7jnzXks03Gv4r/AeuEiKATFAG8EI+MZLgnDdYTuL787Ic7e -KILc+ojtL2H4wtlnjRgcby3f8Qef2EKOE+QjmZxkO66k4PPVdnEgjg+W9nJV6cnW -WhiXwg4BsSBHewPuugoacDO7gfZSpUtAW99eEe5dStyb/VoXce56nwmEV82cMbnK -8jKYDIHQWtqo+BubZfIHApxAV3YPy0Rp5ewULNK8CXsNrN6QCd8L1R7De/sUFMlV -66Tgurh40S32XliZh3eewlpxe4xLD20Z1TCeLeSMRYF8OnEkGSjc2ppBXFk8Azej -UwIDAQAB ------END PUBLIC KEY----- \ No newline at end of file diff --git a/public.pem.b64 b/public.pem.b64 deleted file mode 100644 index ce914ae..0000000 --- a/public.pem.b64 +++ /dev/null @@ -1 +0,0 @@ -LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFrZWtLT2FlWnRuQ0hyOEw4bXNnZwpKMHZlc3c0SmJKbDIxWXRFSDdqbnpYa3MwM0d2NHIvQWV1RWlLQVRGQUc4RUkrTVpMZ25EZFlUdUw3ODdJYzdlCktJTGMrb2p0TDJINHd0bG5qUmdjYnkzZjhRZWYyRUtPRStRam1aeGtPNjZrNFBQVmRuRWdqZytXOW5KVjZjblcKV2hpWHdnNEJzU0JIZXdQdXVnb2FjRE83Z2ZaU3BVdEFXOTllRWU1ZFN0eWIvVm9YY2U1Nm53bUVWODJjTWJuSwo4aktZRElIUVd0cW8rQnViWmZJSEFweEFWM1lQeTBScDVld1VMTks4Q1hzTnJONlFDZDhMMVI3RGUvc1VGTWxWCjY2VGd1cmg0MFMzMlhsaVpoM2Vld2xweGU0eExEMjBaMVRDZUxlU01SWUY4T25Fa0dTamMycHBCWEZrOEF6ZWoKVXdJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..accbbef --- /dev/null +++ b/src/index.ts @@ -0,0 +1,223 @@ +import Observable from "./observable"; +import Lock from "./lock"; + +export interface IFileVersion { + version: string; + time: Date; + preview: string; + deleted: boolean; +} + +export interface IFile { + _id: string; + type: string; + name: string; + folder: string; + deleted: boolean; + active: IFileVersion; + versions: IFileVersion[]; + user: string; + application: string; +} + +export interface IHistory { + file: IFile; + history: IFileVersion[]; +} + + +export class NoConnection extends Error { + type: string; + constructor() { + super("No connection"); + this.type = "noconnection" + } +} + +export class Unauthorized extends Error { + type: string; + constructor() { + super("Not authorized"); + this.type = "unauthorized" + } +} + +export class NoPermission extends Error { + type: string; + constructor() { + super("No permission"); + this.type = "nopermission" + } +} + +export class NotFound extends Error { + type: string; + constructor() { + super("Not found"); + this.type = "notfound" + } +} + +export class BadRequest extends Error { + type: string; + constructor() { + super("Bad request"); + this.type = "badrequest" + } +} + +import * as fetch from "isomorphic-fetch"; + +function statusParser(res: Response) { + if (res.status !== 200) { + switch (res.status) { + case 400: + throw new BadRequest(); + case 404: + throw new NotFound(); + case 403: + throw new NoPermission(); + case 401: + throw new Unauthorized(); + default: + throw new Error(res.statusText); + } + } +} + +export default class SecureFileWrapper { + private _jwtObservableServer: Observable<(jwt: string) => void> = new Observable(); + jwtObservable = this._jwtObservableServer.getPublicApi(); + + jwt: string; + + auth_lock = new Lock(); + + constructor(private server: string) { + if (this.server.endsWith("/")) { + this.server += "api/v1"; + } else { + this.server += "/api/v1"; + } + } + + public async getJWT() { + if (!this.auth_lock.locked) { + let lock = await this.auth_lock.getLock(); + this._jwtObservableServer.send((jwt: string) => { + this.jwt = jwt; + lock.release(); + }); + } + + await this.auth_lock.getLock().then(lock => lock.release()) + } + + public async makeRequest(endpoint: string, method: "POST" | "GET" | "PUT" | "DELETE", query: any, body?: ArrayBuffer | ArrayBufferView, second = false) { + if (!this.jwt || this.jwt === undefined) { + await this.getJWT(); + } + + query.jwt = this.jwt; + let query_str = "?"; + let first = true; + for (let key in query) { + if (!first) query_str += "&"; + query_str += encodeURIComponent(key) + "=" + encodeURIComponent(query[key]); + first = false; + } + var headers = { + "pragme": "no-cache", + "cache-control": "no-cache" + }; + + let body_n; + if (body) { + headers["Content-Type"] = "application/octet-stream" + body_n = Buffer ? Buffer.from(body instanceof ArrayBuffer ? body : body.buffer) : body; + } + try { + let res = await fetch(this.server + endpoint + query_str, { method, body: body_n, headers }); + if (res.status === 401 && !second) { + await this.getJWT(); + return this.makeRequest(endpoint, method, query, body, true); + } else { + statusParser(res); + return res; + } + } catch (err) { + if (err instanceof TypeError || err.errno === "ECONNREFUSED") + throw new NoConnection(); + throw err; + } + } + + // async test(jwt): Promise<{ user: string, test: true }> { + // let res = await this.makeRequest("/test", "GET", {}, undefined, this.jwt_enabled); + // statusParser(res); + // return await res.json(); + // } + + async list(folder?: string): Promise { + let query: any = {} + if (folder) query.folder = folder; + let res = await this.makeRequest("/file", "GET", query); + let d = await res.json(); + return d.files; + } + + async create(name: string, data: ArrayBuffer | ArrayBufferView, type: "text" | "binary", folder?: string, preview?: string): Promise { + let params: any = { type: type, name: name }; + if (preview) { + params.preview = preview; + } + if (folder) { + params.folder = folder; + } + + let res = await this.makeRequest("/file", "POST", params, data); + return (await res.json()).file; + } + + async get(id: string, version?: string): Promise { + let res: Response; + if (typeof version === "string") { + res = await this.makeRequest(`/file/${id}/history/${version}`, "GET", {}); + } else { + res = await this.makeRequest("/file/" + id, "GET", {}); + } + + if (res.arrayBuffer) { + return res.arrayBuffer() + } else { + let blob: Buffer = await (res).buffer() + // console.log(blob.length); + return Uint8Array.from(blob).buffer; + } + } + + async update(id: string, data: ArrayBuffer | ArrayBufferView, preview?: string): Promise { + let put: any = {}; + if (preview) put.preview = preview; + let res = await this.makeRequest("/file/" + id, "PUT", put, data); + + let json = await res.json() + return json.file; + } + + async delete(id: string): Promise { + let res = await this.makeRequest("/file/" + id, "DELETE", {}); + + return res.json(); + } + + async history(id: string): Promise { + let res = await this.makeRequest(`/file/${id}/history`, "GET", {}); + statusParser(res); + return res.json(); + } + + async restore(id: string, version: string) { + await this.makeRequest(`/file/${id}/history/${version}/restore`, "PUT", {}); + } +} \ No newline at end of file diff --git a/src/lock.ts b/src/lock.ts new file mode 100644 index 0000000..5205f78 --- /dev/null +++ b/src/lock.ts @@ -0,0 +1,36 @@ +export type Release = { release: () => void }; +export default class Lock { + private _locked: boolean = false; + get locked() { + return this._locked; + } + private toCome: (() => void)[] = []; + + constructor() { + this.release = this.release.bind(this); + } + + async getLock(): Promise { + if (!this._locked) return { release: this.lock() }; + else { + return new Promise((resolve) => { + this.toCome.push(() => { + resolve({ release: this.lock() }); + }) + }) + } + } + + private lock() { + this._locked = true; + return this.release; + } + + private async release() { + if (this.toCome.length > 0) { + this.toCome.shift()(); + } else { + this._locked = false; + } + } +} \ No newline at end of file diff --git a/src/observable.ts b/src/observable.ts new file mode 100644 index 0000000..f4c6be4 --- /dev/null +++ b/src/observable.ts @@ -0,0 +1,38 @@ +export type ObserverCallback = (data: T) => void; + +export default class Observable { + private subscriber: ObserverCallback[] = []; + private events: T[] = []; + private timeout = undefined; + + constructor(private collect: boolean = true, private collect_intervall: number = 100) { } + + getPublicApi() { + return { + subscribe: (callback: ObserverCallback) => { + if (this.subscriber.indexOf(callback) < 0) + this.subscriber.push(callback) + }, + unsubscribe: (callback: ObserverCallback) => { + let idx = this.subscriber.indexOf(callback); + if (idx >= 0) { + this.subscriber.splice(idx, 1); + } + } + } + } + + send(data: T) { + if (!this.collect) + this.subscriber.forEach(e => e([data])); + else { + this.events.push(data); + if (!this.timeout) { + this.timeout = setTimeout(() => { + this.subscriber.forEach(e => e(this.events)); + this.timeout = 0; + }, this.collect_intervall); + } + } + } +} \ No newline at end of file diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000..f9a47a5 --- /dev/null +++ b/src/test.ts @@ -0,0 +1,130 @@ +import SecureFile, { NotFound } from "./index"; + +import { TextEncoder, TextDecoder } from "util"; + +const testname = "ouiavgbsop687463743" +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); +const testdata = encoder.encode("Ich bin ein Test"); +const newTestData = encoder.encode("neue test daten"); +const testprev = "Ich bin..."; + +const testfolder = "iabos"; +let ftestid; + +import { expect } from "chai" + +function test(sf: SecureFile) { + let testid: string; + let testver: string; + let testver2: string; + + it("create", async () => { + let res = await sf.create(testname, testdata, "text", undefined, testprev) + expect(res, "Res isnnot set").to.exist; + expect(res._id, "Res has no _id").to.exist; + testid = res._id; + testver = res.active.version; + }) + + it("get", async () => { + let res = await sf.get(testid); + expect(res, "No data returned").to.exist; + expect(decoder.decode(res), "Returned data not equal to stored").to.be.equal(decoder.decode(testdata)); + }) + + it("get - fail", async () => { + const inverr = new Error("Should have failed!"); + try { + await sf.get(testid + "asod"); + throw inverr + } catch (err) { + if (err !== inverr) { + expect(err).to.be.instanceOf(NotFound); + } + } + }) + + it("list", async () => { + let res = await sf.list(); + expect(Array.isArray(res), "Is not from type Array").to.be.true; + expect(res.length, "No elements returned").to.greaterThan(0); + let found = !!res.find(e => e._id === testid); + expect(found, "Element not in List").to.be.true; + }) + + it("update", async () => { + let res = await sf.update(testid, newTestData, undefined); + expect(res, "No data returned").to.exist; + expect(res._id, "_id missing").to.exist; + expect(res.active.version, "No new version was created").to.not.equal(testver); + testver2 = res.active.version; + let res2 = await sf.get(testid); + expect(decoder.decode(res2), "Fetched data not updated").to.be.equal(decoder.decode(newTestData)); + }) + + it("history", async () => { + let his = await sf.history(testid); + expect(his, "no data returned").to.exist; + expect(his.file, "file not set").to.exist; + expect(his.history, "history not set").to.exist; + expect(his.history.length, `Not expected history length. Expected 1 got ${his.history.length}`).to.be.equal(1); + + expect(his.history[0].version, "Wrong version on history").to.be.equal(testver); + expect(his.file.active.version, "Wrong version on file").to.be.equal(testver2); + }); + + it("history get old", async () => { + let arch = await sf.get(testid, testver); + expect(decoder.decode(arch), "Old version has wrong data").to.be.equal(decoder.decode(testdata)); + }) + + it("history restore", async () => { + await sf.restore(testid, testver); + + let res = await sf.get(testid); + expect(res, "No data returned").to.exist; + expect(decoder.decode(res), "Returned data not equal to stored").to.be.equal(decoder.decode(testdata)); + }) + + it("delete", async () => { + let res = await sf.delete(testid); + expect(res, "Res not set").to.exist; + }) + + describe("folder", () => { + it("create", async () => { + let res = await sf.create(testname, testdata, "text", testfolder, testprev) + expect(res, "Res not set").to.exist; + expect(res._id, "No _id field").to.exist; + ftestid = res._id; + testver = res.active.version; + }) + + it("list", async () => { + let res = await sf.list(testfolder); + expect(Array.isArray(res), "Is from type Array").to.be.true; + expect(res.length, "Do elements exist?").to.be.greaterThan(0); + let found = false; + res.forEach(e => { + if (e._id === ftestid) { + found = true; + } + }) + expect(found, "Element is not in List").to.exist; + }) + + it("delete", async () => { + let res = await sf.delete(ftestid); + expect(res, "Res not set").to.exist; + }); + }) +} + +describe("SecureFile Tests", function () { + let sf = new SecureFile("http://localhost:3004"); + sf.jwtObservable.subscribe((callback) => { + callback[0]("TESTJWT"); + }) + test(sf) +}) \ No newline at end of file diff --git a/test.d.ts b/test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/test.js b/test.js deleted file mode 100644 index 5ca6eb2..0000000 --- a/test.js +++ /dev/null @@ -1,230 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [0, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -Object.defineProperty(exports, "__esModule", { value: true }); -var index_1 = require("./index"); -var fs_1 = require("fs"); -var buffer_1 = require("buffer"); -var testname = "ouiavgbsop687463743"; -var testdata = new buffer_1.Buffer("Ich bin ein Test"); -var newTestData = new buffer_1.Buffer("neue test daten"); -var testprev = new buffer_1.Buffer("Ich bin..."); -var testmeta = "testaa"; -var testfolder = "iabos"; -var ftestid; -var e_testid; -var private_key; -var chai_1 = require("chai"); -function test(sf) { - var _this = this; - var testid; - var testver; - var testver2; - it("create", function () { return __awaiter(_this, void 0, void 0, function () { - var res; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, sf.create(testname, testdata, "text", undefined, testprev, testmeta)]; - case 1: - res = _a.sent(); - chai_1.assert.ok(res, "Res is not set"); - chai_1.assert.ok(res._id, "Res has not _id"); - testid = res._id; - testver = res.version; - return [2 /*return*/]; - } - }); - }); }); - it("test", function () { return __awaiter(_this, void 0, void 0, function () { - var res; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, sf.test()]; - case 1: - res = _a.sent(); - chai_1.assert.ok(res.test, "Test went wrong"); - chai_1.assert.equal(res.user, "test", "Wrong user returned"); - return [2 /*return*/]; - } - }); - }); }); - it("get", function () { return __awaiter(_this, void 0, void 0, function () { - var res; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, sf.get(testid)]; - case 1: - res = _a.sent(); - chai_1.assert.ok(res, "No date returned"); - chai_1.assert.equal(res.toString(), testdata.toString(), "Returned data not equal to stored"); - return [2 /*return*/]; - } - }); - }); }); - it("list", function () { return __awaiter(_this, void 0, void 0, function () { - var res, found; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, sf.list()]; - case 1: - res = _a.sent(); - chai_1.assert.ok(res); - chai_1.assert.ok(Array.isArray(res), "Is not from type Array"); - chai_1.assert.ok(res.length > 0, "No elements returned"); - found = false; - res.forEach(function (e) { - if (e._id === testid) { - found = true; - chai_1.assert.equal(e.meta, testmeta, "Meta data dows not fit the expected value!"); - } - }); - chai_1.assert.ok(found, "Element not in List"); - return [2 /*return*/]; - } - }); - }); }); - it("update", function () { return __awaiter(_this, void 0, void 0, function () { - var res, res2; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, sf.update(testid, newTestData, undefined, testmeta + "2")]; - case 1: - res = _a.sent(); - chai_1.assert.ok(res, "No data returned"); - chai_1.assert.ok(res._id, "_id missing"); - chai_1.assert.notEqual(res.version, testver, "No new version was created"); - testver2 = res.version; - return [4 /*yield*/, sf.get(testid)]; - case 2: - res2 = _a.sent(); - chai_1.assert.equal(res2.toString(), newTestData.toString(), "Fetched data not updated"); - return [2 /*return*/]; - } - }); - }); }); - it("history", function () { return __awaiter(_this, void 0, void 0, function () { - var his, arch; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, sf.history(testid)]; - case 1: - his = _a.sent(); - chai_1.assert.ok(his, "no data returned"); - chai_1.assert.ok(his.file, "file not set"); - chai_1.assert.ok(his.history, "history not set"); - chai_1.assert.equal(his.history.length, 1, "Not expected history length. Expected 1 got " + his.history.length); - chai_1.assert.equal(his.history[0].version, testver, "Wrong version on history"); - chai_1.assert.equal(his.file.version, testver2, "Wrong version on file"); - return [4 /*yield*/, sf.get(testid, testver)]; - case 2: - arch = _a.sent(); - chai_1.assert.equal(arch.toString(), testdata.toString(), "Old version has wrong data"); - return [2 /*return*/]; - } - }); - }); }); - it("delete", function () { return __awaiter(_this, void 0, void 0, function () { - var res; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, sf.delete(testid)]; - case 1: - res = _a.sent(); - chai_1.assert.ok(res, "Res not set"); - return [2 /*return*/]; - } - }); - }); }); - describe("folder", function () { - it("create", function () { return __awaiter(_this, void 0, void 0, function () { - var res; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, sf.create(testname, testdata, "text", testfolder, testprev)]; - case 1: - res = _a.sent(); - chai_1.assert.ok(res, "Res not set"); - chai_1.assert.ok(res._id, "No _id field"); - ftestid = res._id; - testver = res.version; - return [2 /*return*/]; - } - }); - }); }); - it("list", function () { return __awaiter(_this, void 0, void 0, function () { - var res, found; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, sf.list(testfolder)]; - case 1: - res = _a.sent(); - chai_1.assert.ok(res); - chai_1.assert.ok(Array.isArray(res), "Is from type Array"); - chai_1.assert.ok(res.length > 0, "Do elements exist?"); - found = false; - res.forEach(function (e) { - if (e._id === ftestid) { - found = true; - } - }); - chai_1.assert.ok(found, "Element is not in List"); - return [2 /*return*/]; - } - }); - }); }); - it("delete", function () { return __awaiter(_this, void 0, void 0, function () { - var res; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, sf.delete(ftestid)]; - case 1: - res = _a.sent(); - chai_1.assert.ok(res, "Res not set"); - return [2 /*return*/]; - } - }); - }); }); - }); -} -private_key = fs_1.readFileSync("./private.pem").toString("utf8"); -describe("SecureFile Tests (request based auth)", function () { - var sf = new index_1.default("http://localhost:3005", "test", private_key); - test(sf); -}); -describe("SecureFile Tests (jwt)", function () { - var sf = new index_1.default("http://localhost:3005", "test", private_key, true); - test(sf); -}); diff --git a/test.ts b/test.ts deleted file mode 100644 index 14cdd1d..0000000 --- a/test.ts +++ /dev/null @@ -1,129 +0,0 @@ -import * as rsa from "node-rsa"; -import SecureFile from "./index"; -import { readFileSync } from "fs"; -import { Buffer } from "buffer"; - -const testname = "ouiavgbsop687463743" -const testdata = new Buffer("Ich bin ein Test"); -const newTestData = new Buffer("neue test daten"); -const testprev = new Buffer("Ich bin..."); -const testmeta = "testaa"; - -const testfolder = "iabos"; -let ftestid; - -let e_testid; -let private_key; - -import { assert } from "chai" - -function test(sf: SecureFile) { - let testid: string; - let testver: string; - let testver2: string; - - it("create", async () => { - let res = await sf.create(testname, testdata, "text", undefined, testprev, testmeta) - assert.ok(res, "Res is not set"); - assert.ok(res._id, "Res has not _id"); - testid = res._id; - testver = res.version; - }) - - it("test", async () => { - let res = await sf.test(); - assert.ok(res.test, "Test went wrong"); - assert.equal(res.user, "test", "Wrong user returned"); - }) - - it("get", async () => { - let res = await sf.get(testid); - assert.ok(res, "No date returned"); - assert.equal(res.toString(), testdata.toString(), "Returned data not equal to stored"); - }) - - it("list", async () => { - let res = await sf.list(); - assert.ok(res); - assert.ok(Array.isArray(res), "Is not from type Array"); - assert.ok(res.length > 0, "No elements returned") - let found = false; - res.forEach(e => { - if (e._id === testid) { - found = true; - assert.equal(e.meta, testmeta, "Meta data dows not fit the expected value!") - } - }) - assert.ok(found, "Element not in List") - }) - - it("update", async () => { - let res = await sf.update(testid, newTestData, undefined, testmeta + "2"); - assert.ok(res, "No data returned"); - assert.ok(res._id, "_id missing"); - assert.notEqual(res.version, testver, "No new version was created") - testver2 = res.version; - let res2 = await sf.get(testid); - assert.equal(res2.toString(), newTestData.toString(), "Fetched data not updated") - }) - - it("history", async () => { - let his = await sf.history(testid); - assert.ok(his, "no data returned") - assert.ok(his.file, "file not set") - assert.ok(his.history, "history not set"); - assert.equal(his.history.length, 1, `Not expected history length. Expected 1 got ${his.history.length}`) - - assert.equal(his.history[0].version, testver, "Wrong version on history"); - assert.equal(his.file.version, testver2, "Wrong version on file"); - - let arch = await sf.get(testid, testver); - assert.equal(arch.toString(), testdata.toString(), "Old version has wrong data"); - }); - - it("delete", async () => { - let res = await sf.delete(testid); - assert.ok(res, "Res not set"); - }) - - describe("folder", () => { - it("create", async () => { - let res = await sf.create(testname, testdata, "text", testfolder, testprev) - assert.ok(res, "Res not set"); - assert.ok(res._id, "No _id field"); - ftestid = res._id; - testver = res.version; - }) - - it("list", async () => { - let res = await sf.list(testfolder); - assert.ok(res); - assert.ok(Array.isArray(res), "Is from type Array"); - assert.ok(res.length > 0, "Do elements exist?") - let found = false; - res.forEach(e => { - if (e._id === ftestid) { - found = true; - } - }) - assert.ok(found, "Element is not in List") - }) - - it("delete", async () => { - let res = await sf.delete(ftestid); - assert.ok(res, "Res not set"); - }); - }) -} - -private_key = readFileSync("./private.pem").toString("utf8"); - -describe("SecureFile Tests (request based auth)", function () { - let sf = new SecureFile("http://localhost:3005", "test", private_key); - test(sf) -}) - -describe("SecureFile Tests (jwt)", function () { - let sf = new SecureFile("http://localhost:3005", "test", private_key, true); - test(sf) -}) \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index e0b6efe..13fa4a7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,54 +1,17 @@ { "compilerOptions": { /* Basic Options */ - "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ + "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation: */ - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ "declaration": true, /* Generates corresponding '.d.ts' file. */ "lib": [ "es6", "dom" ], - // "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - // "outDir": "./", /* Redirect output structure to the directory. */ - // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - /* Strict Type-Checking Options */ - //" strict": true /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* Enable strict null checks. */ - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - /* Module Resolution Options */ - // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - /* Source Map Options */ - // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - } + "outDir": "./lib", + "sourceMap": true + }, + "include": [ + "./src" + ] } \ No newline at end of file From 31080c2d9660103699282ee02f60b6d5e2065dc3 Mon Sep 17 00:00:00 2001 From: Fabian Stamm Date: Sun, 23 Dec 2018 18:13:12 +0100 Subject: [PATCH 2/2] Adding new /files endpoint support --- src/index.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/index.ts b/src/index.ts index accbbef..ec25f13 100644 --- a/src/index.ts +++ b/src/index.ts @@ -161,7 +161,7 @@ export default class SecureFileWrapper { async list(folder?: string): Promise { let query: any = {} if (folder) query.folder = folder; - let res = await this.makeRequest("/file", "GET", query); + let res = await this.makeRequest("/files", "GET", query); let d = await res.json(); return d.files; } @@ -175,16 +175,16 @@ export default class SecureFileWrapper { params.folder = folder; } - let res = await this.makeRequest("/file", "POST", params, data); + let res = await this.makeRequest("/files", "POST", params, data); return (await res.json()).file; } async get(id: string, version?: string): Promise { let res: Response; if (typeof version === "string") { - res = await this.makeRequest(`/file/${id}/history/${version}`, "GET", {}); + res = await this.makeRequest(`/files/${id}/history/${version}`, "GET", {}); } else { - res = await this.makeRequest("/file/" + id, "GET", {}); + res = await this.makeRequest("/files/" + id, "GET", {}); } if (res.arrayBuffer) { @@ -199,25 +199,25 @@ export default class SecureFileWrapper { async update(id: string, data: ArrayBuffer | ArrayBufferView, preview?: string): Promise { let put: any = {}; if (preview) put.preview = preview; - let res = await this.makeRequest("/file/" + id, "PUT", put, data); + let res = await this.makeRequest("/files/" + id, "PUT", put, data); let json = await res.json() return json.file; } async delete(id: string): Promise { - let res = await this.makeRequest("/file/" + id, "DELETE", {}); + let res = await this.makeRequest("/files/" + id, "DELETE", {}); return res.json(); } async history(id: string): Promise { - let res = await this.makeRequest(`/file/${id}/history`, "GET", {}); + let res = await this.makeRequest(`/files/${id}/history`, "GET", {}); statusParser(res); return res.json(); } async restore(id: string, version: string) { - await this.makeRequest(`/file/${id}/history/${version}/restore`, "PUT", {}); + await this.makeRequest(`/files/${id}/history/${version}/restore`, "PUT", {}); } } \ No newline at end of file