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); } } }