UNPKG

etebase

Version:

Etebase TypeScript API for the web and node

568 lines 26.5 kB
"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