slate/node_common/utilities.js

350 lines
9.5 KiB
JavaScript

import * as Environment from "~/node_common/environment";
import * as Strings from "~/common/strings";
import * as Validations from "~/common/validations";
import * as Constants from "~/node_common/constants";
import * as Data from "~/node_common/data";
import * as Social from "~/node_common/social";
import * as Logging from "~/common/logging";
import * as ArrayUtilities from "~/node_common/array-utilities";
import * as Monitor from "~/node_common/monitor";
import * as Arrays from "~/common/arrays";
import * as Utilities from "~/common/utilities";
import SearchManager from "~/node_common/managers/search";
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, Filecoin, Client, ThreadID } from "@textile/hub";
const TEXTILE_KEY_INFO = {
key: Environment.TEXTILE_HUB_KEY,
secret: Environment.TEXTILE_HUB_SECRET,
};
export const getIdFromCookieValue = (token) => {
if (!Strings.isEmpty(token)) {
try {
const decoded = JWT.verify(token, Environment.JWT_SECRET);
return decoded.id;
} catch (e) {
Logging.error(e.message);
}
}
};
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"
);
return getIdFromCookieValue(token);
};
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] };
};
export const addExistingCIDToData = async ({ buckets, key, path, cid }) => {
try {
await buckets.setPath(key, path || "/", cid);
return true;
} catch (e) {
return false;
}
};
//NOTE(martina): only use this upon creating a new user. This creates their bucket without checking for an existing bucket
export const createBucket = async ({ bucketName, encrypted = false }) => {
bucketName = bucketName || Constants.textile.mainBucket;
try {
const identity = await PrivateKey.fromRandom();
const textileKey = identity.toString();
let buckets = await Buckets.withKeyInfo(TEXTILE_KEY_INFO);
const textileToken = await buckets.getToken(identity);
buckets.context.withToken(textileToken);
const client = new Client(buckets.context);
const newId = ThreadID.fromRandom();
await client.newDB(newId, Constants.textile.threadName);
const textileThreadID = newId.toString();
buckets.context.withThread(textileThreadID);
const created = await buckets.create(bucketName, { encrypted });
let ipfs = created.root.path;
const textileBucketCID = Strings.ipfsToCid(ipfs);
return {
textileKey,
textileToken,
textileThreadID,
textileBucketCID,
buckets,
bucketKey: created.root.key,
bucketRoot: created.root,
bucketName,
};
} catch (e) {
Logging.error(e?.message);
}
};
//NOTE(martina): only use this for existing users. This grabs their bucket without checking for an existing bucket
export const getBucket = async ({ user, bucketName }) => {
bucketName = bucketName || Constants.textile.mainBucket;
let updateUser = false;
let { textileKey, textileToken, textileThreadID, textileBucketCID } = user;
if (!textileKey) {
return await createBucket({ user, bucketName });
}
let buckets = await Buckets.withKeyInfo(TEXTILE_KEY_INFO);
if (!textileToken) {
const identity = PrivateKey.fromString(textileKey);
textileToken = await buckets.getToken(identity);
updateUser = true;
}
buckets.context.withToken(textileToken);
if (!textileThreadID) {
const client = new Client(buckets.context);
const res = await client.getThread("buckets");
textileThreadID = typeof res.id === "string" ? res.id : ThreadID.fromBytes(res.id).toString();
updateUser = true;
}
buckets.context.withThread(textileThreadID);
const roots = await buckets.list();
const existing = roots.find((bucket) => bucket.name === bucketName);
if (!existing) {
return { buckets: null, bucketKey: null, bucketRoot: null, bucketName };
}
if (!textileBucketCID) {
let ipfs = existing.path;
textileBucketCID = Strings.ipfsToCid(ipfs);
updateUser = true;
}
if (updateUser) {
Data.updateUserById({ id: user.id, textileToken, textileThreadID, textileBucketCID });
}
return {
buckets,
bucketKey: existing.key,
bucketRoot: existing,
bucketName,
};
};
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,
};
};
export const generateRandomNumberInRange = (min, max) =>
Math.floor(Math.random() * (max - min)) + min;
// NOTE(daniel): get all tags on slates and files
export const getUserTags = ({ library }) => {
let tags = new Set();
const isNotEmptyArray = (arr) => Array.isArray(arr) && arr?.length > 0;
library.forEach((item) => {
if (isNotEmptyArray(item.tags)) {
for (let tag of item.tags) {
tags.add(tag);
}
}
});
return Array.from(tags);
};
export const addToSlate = async ({ slate, files, user, saveCopy = false }) => {
let { filteredFiles } = await ArrayUtilities.removeDuplicateSlateFiles({
files,
slate,
});
if (!filteredFiles.length) {
return { added: 0 };
}
let response = await Data.createSlateFiles({ owner: user, slate, files: filteredFiles });
if (!response || response.error) {
return {
decorator: saveCopy
? "SERVER_SAVE_COPY_ADD_TO_SLATE_FAILED"
: "SERVER_CREATE_FILE_ADD_TO_SLATE_FAILED",
added: 0,
};
}
if (saveCopy) {
Monitor.saveCopy({ user, slate, files: filteredFiles });
} else {
Monitor.upload({ user, slate, files: filteredFiles });
}
await Data.updateSlateById({ id: slate.id, updatedAt: new Date() });
if (slate.isPublic) {
addToPublicCollectionUpdatePrivacy({ filteredFiles });
}
addToSlateCheckCoverImage(slate, files);
return { added: response.length };
};
export const removeFromPublicCollectionUpdatePrivacy = async ({ files }) => {
let targetFiles = Arrays.filterPublic(files);
let madePrivate = [];
for (let file of targetFiles) {
let updatedFile = await Data.recalcFilePrivacy({ fileId: file.id });
if (!updatedFile) continue;
if (file.isPublic && !updatedFile.isPublic) {
madePrivate.push(updatedFile);
}
}
return madePrivate;
};
export const addToPublicCollectionUpdatePrivacy = async ({ files }) => {
let targetFiles = Arrays.filterPrivate(files);
let madePublic = [];
for (let file of targetFiles) {
let updatedFile = await Data.recalcFilePrivacy({ fileId: file.id });
if (!updatedFile) continue;
if (!file.isPublic && updatedFile.isPublic) {
madePublic.push(updatedFile);
}
}
return madePublic;
};
export const selectSlateCoverImage = (objects) => {
let selectedObject;
if (!objects.length) return null;
for (let object of objects) {
if (Utilities.getImageUrlIfExists(object)) {
selectedObject = object;
break;
}
}
selectedObject = objects[0];
let { tags, oldData, ...cleanedObject } = selectedObject;
return cleanedObject;
};
export const addToSlateCheckCoverImage = async (slate, filesAdded) => {
if (Utilities.getImageUrlIfExists(slate.coverImage)) {
return;
}
if (!filesAdded) return;
let files;
if (Array.isArray(filesAdded)) {
files = filesAdded;
} else {
files = [filesAdded];
}
let newObjects = (slate.objects || []).concat(files);
let coverImage = selectSlateCoverImage(newObjects);
if (coverImage?.id !== slate.coverImage?.id) {
slate.coverImage = coverImage;
let updatedSlate = await Data.updateSlateById(slate);
await SearchManager.updateSlate(updatedSlate);
}
};
export const removeFromSlateCheckCoverImage = async (slate, fileIdsRemoved) => {
if (!slate.coverImage) return;
let fileIds;
if (Array.isArray(fileIdsRemoved)) {
fileIds = fileIdsRemoved;
} else {
fileIds = [fileIdsRemoved];
}
for (let fileId of fileIds) {
if (fileId === slate.coverImage.id) {
let newObjects = slate.objects.filter((obj) => !fileIds.includes(obj.id));
let coverImage = selectSlateCoverImage(newObjects);
slate.coverImage = coverImage;
let updatedSlate = await Data.updateSlateById(slate);
await SearchManager.updateSlate(updatedSlate);
return;
}
}
};