etebase
Version:
Etebase TypeScript API for the web and node
568 lines • 26.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Item = exports.Collection = exports.OutputFormat = exports.CollectionMemberManager = exports.CollectionInvitationManager = exports.ItemManager = exports.CollectionManager = exports.Account = void 0;
const urijs_1 = __importDefault(require("urijs"));
const Constants = __importStar(require("./Constants"));
const Crypto_1 = require("./Crypto");
var Crypto_2 = require("./Crypto");
Object.defineProperty(exports, "ready", { enumerable: true, get: function () { return Crypto_2.ready; } });
Object.defineProperty(exports, "getPrettyFingerprint", { enumerable: true, get: function () { return Crypto_2.getPrettyFingerprint; } });
Object.defineProperty(exports, "_setRnSodium", { enumerable: true, get: function () { return Crypto_2._setRnSodium; } });
Object.defineProperty(exports, "deriveKey", { enumerable: true, get: function () { return Crypto_2.deriveKey; } });
Object.defineProperty(exports, "KeyDerivationDifficulty", { enumerable: true, get: function () { return Crypto_2.KeyDerivationDifficulty; } });
const Exceptions_1 = require("./Exceptions");
__exportStar(require("./Exceptions"), exports);
const Helpers_1 = require("./Helpers");
var Helpers_2 = require("./Helpers");
Object.defineProperty(exports, "fromBase64", { enumerable: true, get: function () { return Helpers_2.fromBase64; } });
Object.defineProperty(exports, "toBase64", { enumerable: true, get: function () { return Helpers_2.toBase64; } });
Object.defineProperty(exports, "randomBytes", { enumerable: true, get: function () { return Helpers_2.randomBytes; } });
const EncryptedModels_1 = require("./EncryptedModels");
__exportStar(require("./EncryptedModels"), exports); // FIXME: cherry-pick what we export
const OnlineManagers_1 = require("./OnlineManagers");
const Exceptions_2 = require("./Exceptions");
const Constants_1 = require("./Constants");
var Constants_2 = require("./Constants");
Object.defineProperty(exports, "CURRENT_VERSION", { enumerable: true, get: function () { return Constants_2.CURRENT_VERSION; } });
class Account {
constructor(mainEncryptionKey, version) {
this.mainKey = mainEncryptionKey;
this.version = version;
this.authToken = null;
}
static async isEtebaseServer(serverUrl) {
const authenticator = new OnlineManagers_1.Authenticator(serverUrl);
return authenticator.isEtebase();
}
static async signup(user, password, serverUrl) {
await Crypto_1.ready;
serverUrl = serverUrl !== null && serverUrl !== void 0 ? serverUrl : Constants.SERVER_URL;
const authenticator = new OnlineManagers_1.Authenticator(serverUrl);
const version = Constants_1.CURRENT_VERSION;
const salt = Helpers_1.randomBytes(32);
const mainKey = await Crypto_1.deriveKey(salt, password);
const mainCryptoManager = EncryptedModels_1.getMainCryptoManager(mainKey, version);
const loginCryptoManager = mainCryptoManager.getLoginCryptoManager();
const identityCryptoManager = Crypto_1.BoxCryptoManager.keygen();
const accountKey = Helpers_1.randomBytes(Helpers_1.symmetricKeyLength);
const encryptedContent = mainCryptoManager.encrypt(Crypto_1.concatArrayBuffers(accountKey, identityCryptoManager.privkey));
const loginResponse = await authenticator.signup(user, salt, loginCryptoManager.pubkey, identityCryptoManager.pubkey, encryptedContent);
const ret = new this(mainKey, version);
ret.user = loginResponse.user;
ret.authToken = loginResponse.token;
ret.serverUrl = serverUrl;
return ret;
}
static async login(username, password, serverUrl) {
var _a;
await Crypto_1.ready;
serverUrl = serverUrl !== null && serverUrl !== void 0 ? serverUrl : Constants.SERVER_URL;
const authenticator = new OnlineManagers_1.Authenticator(serverUrl);
let loginChallenge;
try {
loginChallenge = await authenticator.getLoginChallenge(username);
}
catch (e) {
if ((e instanceof Exceptions_1.UnauthorizedError) && (((_a = e.content) === null || _a === void 0 ? void 0 : _a.code) === "user_not_init")) {
const user = {
username,
email: "init@localhost",
};
return this.signup(user, password, serverUrl);
}
throw e;
}
const mainKey = await Crypto_1.deriveKey(loginChallenge.salt, password);
const mainCryptoManager = EncryptedModels_1.getMainCryptoManager(mainKey, loginChallenge.version);
const loginCryptoManager = mainCryptoManager.getLoginCryptoManager();
const response = Helpers_1.msgpackEncode({
username,
challenge: loginChallenge.challenge,
host: urijs_1.default(serverUrl).host(),
action: "login",
});
const loginResponse = await authenticator.login(response, loginCryptoManager.signDetached(response));
const ret = new this(mainKey, loginChallenge.version);
ret.user = loginResponse.user;
ret.authToken = loginResponse.token;
ret.serverUrl = serverUrl;
return ret;
}
async fetchToken() {
const serverUrl = this.serverUrl;
const authenticator = new OnlineManagers_1.Authenticator(serverUrl);
const username = this.user.username;
const loginChallenge = await authenticator.getLoginChallenge(username);
const mainKey = this.mainKey;
const mainCryptoManager = EncryptedModels_1.getMainCryptoManager(mainKey, loginChallenge.version);
const loginCryptoManager = mainCryptoManager.getLoginCryptoManager();
const response = Helpers_1.msgpackEncode({
username,
challenge: loginChallenge.challenge,
host: urijs_1.default(serverUrl).host(),
action: "login",
});
const loginResponse = await authenticator.login(response, loginCryptoManager.signDetached(response));
this.authToken = loginResponse.token;
}
async logout() {
const authenticator = new OnlineManagers_1.Authenticator(this.serverUrl);
authenticator.logout(this.authToken);
this.version = -1;
this.mainKey = new Uint8Array();
this.authToken = null;
}
async changePassword(password) {
const serverUrl = this.serverUrl;
const authenticator = new OnlineManagers_1.Authenticator(serverUrl);
const username = this.user.username;
const loginChallenge = await authenticator.getLoginChallenge(username);
const oldMainCryptoManager = EncryptedModels_1.getMainCryptoManager(this.mainKey, this.version);
const content = oldMainCryptoManager.decrypt(this.user.encryptedContent);
const oldLoginCryptoManager = oldMainCryptoManager.getLoginCryptoManager();
const mainKey = await Crypto_1.deriveKey(loginChallenge.salt, password);
const mainCryptoManager = EncryptedModels_1.getMainCryptoManager(mainKey, this.version);
const loginCryptoManager = mainCryptoManager.getLoginCryptoManager();
const encryptedContent = mainCryptoManager.encrypt(content);
const response = Helpers_1.msgpackEncode({
username,
challenge: loginChallenge.challenge,
host: urijs_1.default(serverUrl).host(),
action: "changePassword",
loginPubkey: loginCryptoManager.pubkey,
encryptedContent: encryptedContent,
});
await authenticator.changePassword(this.authToken, response, oldLoginCryptoManager.signDetached(response));
this.mainKey = mainKey;
this.user.encryptedContent = encryptedContent;
}
async getDashboardUrl() {
const serverUrl = this.serverUrl;
const authenticator = new OnlineManagers_1.Authenticator(serverUrl);
return await authenticator.getDashboardUrl(this.authToken);
}
async save(encryptionKey_) {
const version = Constants_1.CURRENT_VERSION;
const encryptionKey = encryptionKey_ !== null && encryptionKey_ !== void 0 ? encryptionKey_ : new Uint8Array(32);
const cryptoManager = new EncryptedModels_1.StorageCryptoManager(encryptionKey, version);
const content = {
user: this.user,
authToken: this.authToken,
serverUrl: this.serverUrl,
version: this.version,
key: cryptoManager.encrypt(this.mainKey),
};
const ret = {
version,
encryptedData: cryptoManager.encrypt(Helpers_1.msgpackEncode(content), new Uint8Array([version])),
};
return Helpers_1.toBase64(Helpers_1.msgpackEncode(ret));
}
static async restore(accountDataStored_, encryptionKey_) {
var _a;
await Crypto_1.ready;
const encryptionKey = encryptionKey_ !== null && encryptionKey_ !== void 0 ? encryptionKey_ : new Uint8Array(32);
const accountDataStored = Helpers_1.msgpackDecode(Helpers_1.fromBase64(accountDataStored_));
const cryptoManager = new EncryptedModels_1.StorageCryptoManager(encryptionKey, accountDataStored.version);
const accountData = Helpers_1.msgpackDecode(cryptoManager.decrypt(accountDataStored.encryptedData, new Uint8Array([accountDataStored.version])));
const ret = new this(cryptoManager.decrypt(accountData.key), accountData.version);
ret.user = accountData.user;
ret.authToken = (_a = accountData.authToken) !== null && _a !== void 0 ? _a : null;
ret.serverUrl = accountData.serverUrl;
return ret;
}
getCollectionManager() {
return new CollectionManager(this);
}
getInvitationManager() {
return new CollectionInvitationManager(this);
}
_getCryptoManager() {
// FIXME: cache this
const mainCryptoManager = EncryptedModels_1.getMainCryptoManager(this.mainKey, this.version);
const content = mainCryptoManager.decrypt(this.user.encryptedContent);
return mainCryptoManager.getAccountCryptoManager(content.subarray(0, Helpers_1.symmetricKeyLength));
}
_getIdentityCryptoManager() {
// FIXME: cache this
const mainCryptoManager = EncryptedModels_1.getMainCryptoManager(this.mainKey, this.version);
const content = mainCryptoManager.decrypt(this.user.encryptedContent);
return mainCryptoManager.getIdentityCryptoManager(content.subarray(Helpers_1.symmetricKeyLength));
}
}
exports.Account = Account;
const defaultCacheOptions = {
saveContent: true,
};
class CollectionManager {
constructor(etebase) {
this.etebase = etebase;
this.onlineManager = new OnlineManagers_1.CollectionManagerOnline(this.etebase);
}
async create(colType, meta, content) {
const uintcontent = (content instanceof Uint8Array) ? content : Helpers_1.fromString(content);
const mainCryptoManager = this.etebase._getCryptoManager();
const encryptedCollection = await EncryptedModels_1.EncryptedCollection.create(mainCryptoManager, colType, meta, uintcontent);
return new Collection(encryptedCollection.getCryptoManager(mainCryptoManager), encryptedCollection);
}
async fetch(colUid, options) {
const mainCryptoManager = this.etebase._getCryptoManager();
const encryptedCollection = await this.onlineManager.fetch(colUid, options);
return new Collection(encryptedCollection.getCryptoManager(mainCryptoManager), encryptedCollection);
}
async list(colType, options) {
const mainCryptoManager = this.etebase._getCryptoManager();
const colTypes = (Array.isArray(colType)) ? colType : [colType];
const collectionTypes = colTypes.map((x) => mainCryptoManager.colTypeToUid(x));
const ret = await this.onlineManager.list(collectionTypes, options);
return {
...ret,
data: ret.data.map((x) => new Collection(x.getCryptoManager(mainCryptoManager), x)),
};
}
async upload(collection, options) {
const col = collection.encryptedCollection;
// If we have a etag, it means we previously fetched it.
if (col.lastEtag) {
const itemOnlineManager = new OnlineManagers_1.CollectionItemManagerOnline(this.etebase, col.uid);
await itemOnlineManager.batch([col.item], undefined, options);
}
else {
await this.onlineManager.create(col, options);
}
col.__markSaved();
}
async transaction(collection, options) {
const col = collection.encryptedCollection;
// If we have a etag, it means we previously fetched it.
if (col.lastEtag) {
const itemOnlineManager = new OnlineManagers_1.CollectionItemManagerOnline(this.etebase, col.uid);
await itemOnlineManager.transaction([col.item], undefined, options);
}
else {
await this.onlineManager.create(col, options);
}
col.__markSaved();
}
cacheSave(collection, options = defaultCacheOptions) {
return collection.encryptedCollection.cacheSave(options.saveContent);
}
cacheLoad(cache) {
const encCol = EncryptedModels_1.EncryptedCollection.cacheLoad(cache);
const mainCryptoManager = this.etebase._getCryptoManager();
return new Collection(encCol.getCryptoManager(mainCryptoManager), encCol);
}
getItemManager(col_) {
const col = col_.encryptedCollection;
const collectionCryptoManager = col.getCryptoManager(this.etebase._getCryptoManager());
return new ItemManager(this.etebase, collectionCryptoManager, col.uid);
}
getMemberManager(col) {
return new CollectionMemberManager(this.etebase, this, col.encryptedCollection);
}
}
exports.CollectionManager = CollectionManager;
class ItemManager {
constructor(etebase, collectionCryptoManager, colUid) {
this.collectionCryptoManager = collectionCryptoManager;
this.onlineManager = new OnlineManagers_1.CollectionItemManagerOnline(etebase, colUid);
this.collectionUid = colUid;
}
async create(meta, content) {
const uintcontent = (content instanceof Uint8Array) ? content : Helpers_1.fromString(content);
const encryptedItem = await EncryptedModels_1.EncryptedCollectionItem.create(this.collectionCryptoManager, meta, uintcontent);
return new Item(this.collectionUid, encryptedItem.getCryptoManager(this.collectionCryptoManager), encryptedItem);
}
async fetch(itemUid, options) {
const encryptedItem = await this.onlineManager.fetch(itemUid, options);
return new Item(this.collectionUid, encryptedItem.getCryptoManager(this.collectionCryptoManager), encryptedItem);
}
async list(options) {
const ret = await this.onlineManager.list(options);
return {
...ret,
data: ret.data.map((x) => new Item(this.collectionUid, x.getCryptoManager(this.collectionCryptoManager), x)),
};
}
async itemRevisions(item, options) {
const ret = await this.onlineManager.itemRevisions(item.encryptedItem, options);
return {
...ret,
data: ret.data.map((x) => new Item(this.collectionUid, x.getCryptoManager(this.collectionCryptoManager), x)),
};
}
// Prepare the items for upload and verify they belong to the right collection
itemsPrepareForUpload(items) {
return items === null || items === void 0 ? void 0 : items.map((x) => {
if (x.collectionUid !== this.collectionUid) {
throw new Exceptions_2.ProgrammingError(`Uploading an item belonging to collection ${x.collectionUid} to another collection (${this.collectionUid}) is not allowed!`);
}
return x.encryptedItem;
});
}
async fetchUpdates(items, options) {
const ret = await this.onlineManager.fetchUpdates(this.itemsPrepareForUpload(items), options);
return {
...ret,
data: ret.data.map((x) => new Item(this.collectionUid, x.getCryptoManager(this.collectionCryptoManager), x)),
};
}
async fetchMulti(items, options) {
const ret = await this.onlineManager.fetchMulti(items, options);
return {
...ret,
data: ret.data.map((x) => new Item(this.collectionUid, x.getCryptoManager(this.collectionCryptoManager), x)),
};
}
async batch(items, deps, options) {
await this.onlineManager.batch(this.itemsPrepareForUpload(items), this.itemsPrepareForUpload(deps), options);
items.forEach((item) => {
item.encryptedItem.__markSaved();
});
}
async transaction(items, deps, options) {
await this.onlineManager.transaction(this.itemsPrepareForUpload(items), this.itemsPrepareForUpload(deps), options);
items.forEach((item) => {
item.encryptedItem.__markSaved();
});
}
async uploadContent(item) {
const [encryptedItem] = this.itemsPrepareForUpload([item]);
const pendingChunks = encryptedItem.__getPendingChunks();
for (const chunk of pendingChunks) {
// FIXME: Upload multiple in parallel
try {
await this.onlineManager.chunkUpload(encryptedItem, chunk);
}
catch (e) {
if (e instanceof Exceptions_1.ConflictError) {
// Skip if we arleady have the chunk
continue;
}
throw e;
}
}
}
async downloadContent(item) {
const [encryptedItem] = this.itemsPrepareForUpload([item]);
const missingChunks = encryptedItem.__getMissingChunks();
for (const chunk of missingChunks) {
if (!chunk[1]) {
// FIXME: Download in parallel
chunk[1] = await this.onlineManager.chunkDownload(encryptedItem, chunk[0]);
}
}
}
async subscribeChanges(cb, options) {
return this.onlineManager.subscribeChanges(async (ret) => {
cb({
...ret,
data: ret.data.map((x) => new Item(this.collectionUid, x.getCryptoManager(this.collectionCryptoManager), x)),
});
}, options);
}
cacheSave(item, options = defaultCacheOptions) {
return item.encryptedItem.cacheSave(options.saveContent);
}
cacheLoad(cache) {
const encItem = EncryptedModels_1.EncryptedCollectionItem.cacheLoad(cache);
return new Item(this.collectionUid, encItem.getCryptoManager(this.collectionCryptoManager), encItem);
}
}
exports.ItemManager = ItemManager;
class CollectionInvitationManager {
constructor(etebase) {
this.etebase = etebase;
this.onlineManager = new OnlineManagers_1.CollectionInvitationManagerOnline(this.etebase);
}
async listIncoming(options) {
return await this.onlineManager.listIncoming(options);
}
async listOutgoing(options) {
return await this.onlineManager.listOutgoing(options);
}
async accept(invitation) {
const mainCryptoManager = this.etebase._getCryptoManager();
const identCryptoManager = this.etebase._getIdentityCryptoManager();
const content = Helpers_1.msgpackDecode(Helpers_1.bufferUnpad(identCryptoManager.decrypt(invitation.signedEncryptionKey, invitation.fromPubkey)));
const colTypeUid = mainCryptoManager.colTypeToUid(content.collectionType);
const encryptedEncryptionKey = mainCryptoManager.encrypt(content.encryptionKey, colTypeUid);
return this.onlineManager.accept(invitation, colTypeUid, encryptedEncryptionKey);
}
async reject(invitation) {
return this.onlineManager.reject(invitation);
}
async fetchUserProfile(username) {
return await this.onlineManager.fetchUserProfile(username);
}
async invite(col, username, pubkey, accessLevel) {
const mainCryptoManager = this.etebase._getCryptoManager();
const identCryptoManager = this.etebase._getIdentityCryptoManager();
const invitation = await col.encryptedCollection.createInvitation(mainCryptoManager, identCryptoManager, username, pubkey, accessLevel);
await this.onlineManager.invite(invitation);
}
async disinvite(invitation) {
return this.onlineManager.disinvite(invitation);
}
get pubkey() {
const identCryptoManager = this.etebase._getIdentityCryptoManager();
return identCryptoManager.pubkey;
}
}
exports.CollectionInvitationManager = CollectionInvitationManager;
class CollectionMemberManager {
constructor(etebase, _collectionManager, encryptedCollection) {
this.etebase = etebase;
this.onlineManager = new OnlineManagers_1.CollectionMemberManagerOnline(this.etebase, encryptedCollection.uid);
}
async list(options) {
return this.onlineManager.list(options);
}
async remove(username) {
return this.onlineManager.remove(username);
}
async leave() {
return this.onlineManager.leave();
}
async modifyAccessLevel(username, accessLevel) {
return this.onlineManager.modifyAccessLevel(username, accessLevel);
}
}
exports.CollectionMemberManager = CollectionMemberManager;
var OutputFormat;
(function (OutputFormat) {
OutputFormat[OutputFormat["Uint8Array"] = 0] = "Uint8Array";
OutputFormat[OutputFormat["String"] = 1] = "String";
})(OutputFormat = exports.OutputFormat || (exports.OutputFormat = {}));
class Collection {
constructor(cryptoManager, encryptedCollection) {
this.cryptoManager = cryptoManager;
this.encryptedCollection = encryptedCollection;
}
verify() {
return this.encryptedCollection.verify(this.cryptoManager);
}
setMeta(meta) {
this.encryptedCollection.setMeta(this.cryptoManager, meta);
}
getMeta() {
return this.encryptedCollection.getMeta(this.cryptoManager);
}
async setContent(content) {
const uintcontent = (content instanceof Uint8Array) ? content : Helpers_1.fromString(content);
await this.encryptedCollection.setContent(this.cryptoManager, uintcontent);
}
async getContent(outputFormat = OutputFormat.Uint8Array) {
const ret = await this.encryptedCollection.getContent(this.cryptoManager);
switch (outputFormat) {
case OutputFormat.Uint8Array:
return ret;
case OutputFormat.String:
return Helpers_1.toString(ret);
default:
throw new Error("Bad output format");
}
}
delete(preserveContent = false) {
this.encryptedCollection.delete(this.cryptoManager, preserveContent);
}
get uid() {
return this.encryptedCollection.uid;
}
get etag() {
return this.encryptedCollection.etag;
}
get isDeleted() {
return this.encryptedCollection.isDeleted;
}
get stoken() {
return this.encryptedCollection.stoken;
}
get accessLevel() {
return this.encryptedCollection.accessLevel;
}
getCollectionType() {
return this.encryptedCollection.getCollectionType(this.cryptoManager.accountCryptoManager);
}
get item() {
const encryptedItem = this.encryptedCollection.item;
return new Item(this.uid, encryptedItem.getCryptoManager(this.cryptoManager), encryptedItem);
}
}
exports.Collection = Collection;
class Item {
constructor(collectionUid, cryptoManager, encryptedItem) {
this.cryptoManager = cryptoManager;
this.encryptedItem = encryptedItem;
this.collectionUid = collectionUid;
}
verify() {
return this.encryptedItem.verify(this.cryptoManager);
}
setMeta(meta) {
this.encryptedItem.setMeta(this.cryptoManager, meta);
}
getMeta() {
return this.encryptedItem.getMeta(this.cryptoManager);
}
async setContent(content) {
const uintcontent = (content instanceof Uint8Array) ? content : Helpers_1.fromString(content);
await this.encryptedItem.setContent(this.cryptoManager, uintcontent);
}
async getContent(outputFormat = OutputFormat.Uint8Array) {
const ret = await this.encryptedItem.getContent(this.cryptoManager);
switch (outputFormat) {
case OutputFormat.Uint8Array:
return ret;
case OutputFormat.String:
return Helpers_1.toString(ret);
default:
throw new Error("Bad output format");
}
}
delete(preserveContent = false) {
this.encryptedItem.delete(this.cryptoManager, preserveContent);
}
get uid() {
return this.encryptedItem.uid;
}
get etag() {
return this.encryptedItem.etag;
}
get isDeleted() {
return this.encryptedItem.isDeleted;
}
get isMissingContent() {
return this.encryptedItem.isMissingContent;
}
_clone() {
return new Item(this.collectionUid, this.cryptoManager, EncryptedModels_1.EncryptedCollectionItem.deserialize(this.encryptedItem.serialize()));
}
}
exports.Item = Item;
//# sourceMappingURL=Etebase.js.map