import * as Environment from "~/node_common/environment"; import * as Strings from "~/common/strings"; import * as Constants from "~/node_common/constants"; import * as Social from "~/node_common/social"; import crypto from "crypto"; import JWT from "jsonwebtoken"; import BCrypt from "bcrypt"; const ENCRYPTION_ALGORITHM = "aes-256-ctr"; const ENCRYPTION_IV = crypto.randomBytes(16); import { Buckets, PrivateKey, Pow, Client, ThreadID } from "@textile/hub"; const BUCKET_NAME = "data"; const TEXTILE_KEY_INFO = { key: Environment.TEXTILE_HUB_KEY, secret: Environment.TEXTILE_HUB_SECRET, }; export const checkTextile = async () => { try { const response = await fetch("https://slate.textile.io/health", { headers: { Accept: "application/json", "Content-Type": "application/json", }, }); if (response.status === 204) { return true; } Social.sendTextileSlackMessage({ file: "/node_common/utilities.js", user: { username: "UNDEFINED" }, message: "https://slate.textile.io/health is down", code: "N/A", functionName: `checkTextile`, }); } catch (e) { Social.sendTextileSlackMessage({ file: "/node_common/utilities.js", user: { username: "UNDEFINED" }, message: e.message, code: e.code, functionName: `checkTextile`, }); } return false; }; export const getIdFromCookie = (req) => { let id = null; if (Strings.isEmpty(req.headers.cookie)) { return id; } const token = req.headers.cookie.replace( /(?:(?:^|.*;\s*)WEB_SERVICE_SESSION_KEY\s*\=\s*([^;]*).*$)|^.*$/, "$1" ); if (!Strings.isEmpty(token)) { try { const decoded = JWT.verify(token, Environment.JWT_SECRET); id = decoded.id; } catch (e) { console.log(e.message); } } return id; }; export const encryptWithSecret = async (text, secret) => { const cipher = crypto.createCipheriv(ENCRYPTION_ALGORITHM, secret, ENCRYPTION_IV); const encrypted = Buffer.concat([cipher.update(text), cipher.final()]); return { iv: ENCRYPTION_IV.toString("hex"), hex: encrypted.toString("hex"), }; }; export const encryptPassword = async (text, salt) => { if (!text) { return null; } let hash = text; for (let i = 0; i < Environment.LOCAL_PASSWORD_ROUNDS_MANUAL; i++) { hash = await BCrypt.hash(hash, salt); } hash = await BCrypt.hash(hash, Environment.LOCAL_PASSWORD_SECRET); return hash; }; export const parseAuthHeader = (value) => { if (typeof value !== "string") { return null; } var matches = value.match(/(\S+)\s+(\S+)/); return matches && { scheme: matches[1], value: matches[2] }; }; // NOTE(jim): Requires @textile/hub export const getPowergateAPIFromUserToken = async ({ user }) => { const token = user.data.tokens.api; const identity = await PrivateKey.fromString(token); const power = await Pow.withKeyInfo(TEXTILE_KEY_INFO); await power.getToken(identity); // TODO(jim): Put this call into a file for all Textile related calls. let info = {}; try { const powerInfoResponse = await power.info(); info = powerInfoResponse.info; } catch (e) { Social.sendTextileSlackMessage({ file: "/node_common/utilities.js", user, message: e.message, code: e.code, functionName: `power.info`, }); } // TODO(jim): Put this call into a file for all Textile related calls. let health = {}; try { health = await power.health(); } catch (e) { Social.sendTextileSlackMessage({ file: "/node_common/utilities.js", user, message: e.message, code: e.code, functionName: `power.health`, }); } return { power, powerHealth: health, powerInfo: info, }; }; export const setupWithThread = async ({ buckets }) => { const client = new Client(buckets.context); try { const res = await client.getThread("buckets"); buckets.withThread(res.id.toString()); } catch (error) { if (error.message !== "Thread not found") { throw new Error(error.message); } const newId = ThreadID.fromRandom(); await client.newDB(newId, "buckets"); const threadID = newId.toString(); buckets.withThread(threadID); } return buckets; }; export const addExistingCIDToData = async ({ buckets, key, path, cid }) => { try { await buckets.setPath(key, path || "/", cid); return true; } catch (e) { return false; } }; // NOTE(jim): Requires @textile/hub export const getBucketAPIFromUserToken = async ({ user, bucketName, encrypted = false }) => { const token = user.data.tokens.api; const name = Strings.isEmpty(bucketName) ? BUCKET_NAME : bucketName; const identity = await PrivateKey.fromString(token); let buckets = await Buckets.withKeyInfo(TEXTILE_KEY_INFO); await buckets.getToken(identity); let root = null; console.log(`[ buckets ] getOrCreate init ${name}`); // NOTE(jim): captures `withThread` cases. try { buckets = await setupWithThread({ buckets }); } catch (e) { console.log(`[ textile ] warning: ${e.message}`); } console.log(`[ buckets ] getOrCreate thread found for ${name}`); // NOTE(jim): captures finding your bucket and or creating a new one. try { const roots = await buckets.list(); root = roots.find((bucket) => bucket.name === name); if (!root) { console.log(`[ buckets ] creating new bucket ${name}`); if (encrypted) { console.log("[ buckets ] this bucket will be encrypted"); } const created = await buckets.create(name, encrypted); root = created.root; } } catch (e) { Social.sendTextileSlackMessage({ file: "/node_common/utilities.js", user, message: e.message, code: e.code, functionName: `buckets.getOrCreate`, }); return { buckets: null, bucketKey: null, bucketRoot: null }; } console.log(`[ buckets ] getOrCreate success for ${name}`); return { buckets, bucketKey: root.key, bucketRoot: root, bucketName: name, }; }; export const getFileName = (s) => { let target = s; if (target.endsWith("/")) { target = target.substring(0, target.length - 1); } return target.substr(target.lastIndexOf("/") + 1); }; export const createFolder = ({ id, name }) => { return { decorator: "FOLDER", id, folderId: id, icon: "FOLDER", name: name ? name : getFileName(id), pageTitle: `Exploring ${getFileName(id)}`, date: null, size: null, children: [], }; }; export const updateStateData = async (state, newState) => { return { ...state, ...newState, }; };