UNPKG

etebase

Version:

Etebase TypeScript API for the web and node

524 lines 23.2 kB
import URI from "urijs"; import * as Constants from "./Constants"; import { deriveKey, concatArrayBuffers, BoxCryptoManager, ready } from "./Crypto"; export { ready, getPrettyFingerprint, _setRnSodium, deriveKey, KeyDerivationDifficulty } from "./Crypto"; import { ConflictError, UnauthorizedError } from "./Exceptions"; export * from "./Exceptions"; import { fromBase64, toBase64, fromString, toString, randomBytes, symmetricKeyLength, msgpackEncode, msgpackDecode, bufferUnpad } from "./Helpers"; export { fromBase64, toBase64, randomBytes } from "./Helpers"; import { EncryptedCollection, EncryptedCollectionItem, getMainCryptoManager, StorageCryptoManager, } from "./EncryptedModels"; export * from "./EncryptedModels"; // FIXME: cherry-pick what we export import { Authenticator, CollectionManagerOnline, CollectionItemManagerOnline, CollectionInvitationManagerOnline, CollectionMemberManagerOnline, } from "./OnlineManagers"; import { ProgrammingError } from "./Exceptions"; import { CURRENT_VERSION } from "./Constants"; export { CURRENT_VERSION } from "./Constants"; export class Account { constructor(mainEncryptionKey, version) { this.mainKey = mainEncryptionKey; this.version = version; this.authToken = null; } static async isEtebaseServer(serverUrl) { const authenticator = new Authenticator(serverUrl); return authenticator.isEtebase(); } static async signup(user, password, serverUrl) { await ready; serverUrl = serverUrl !== null && serverUrl !== void 0 ? serverUrl : Constants.SERVER_URL; const authenticator = new Authenticator(serverUrl); const version = CURRENT_VERSION; const salt = randomBytes(32); const mainKey = await deriveKey(salt, password); const mainCryptoManager = getMainCryptoManager(mainKey, version); const loginCryptoManager = mainCryptoManager.getLoginCryptoManager(); const identityCryptoManager = BoxCryptoManager.keygen(); const accountKey = randomBytes(symmetricKeyLength); const encryptedContent = mainCryptoManager.encrypt(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 ready; serverUrl = serverUrl !== null && serverUrl !== void 0 ? serverUrl : Constants.SERVER_URL; const authenticator = new Authenticator(serverUrl); let loginChallenge; try { loginChallenge = await authenticator.getLoginChallenge(username); } catch (e) { if ((e instanceof 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 deriveKey(loginChallenge.salt, password); const mainCryptoManager = getMainCryptoManager(mainKey, loginChallenge.version); const loginCryptoManager = mainCryptoManager.getLoginCryptoManager(); const response = msgpackEncode({ username, challenge: loginChallenge.challenge, host: URI(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 Authenticator(serverUrl); const username = this.user.username; const loginChallenge = await authenticator.getLoginChallenge(username); const mainKey = this.mainKey; const mainCryptoManager = getMainCryptoManager(mainKey, loginChallenge.version); const loginCryptoManager = mainCryptoManager.getLoginCryptoManager(); const response = msgpackEncode({ username, challenge: loginChallenge.challenge, host: URI(serverUrl).host(), action: "login", }); const loginResponse = await authenticator.login(response, loginCryptoManager.signDetached(response)); this.authToken = loginResponse.token; } async logout() { const authenticator = new 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 Authenticator(serverUrl); const username = this.user.username; const loginChallenge = await authenticator.getLoginChallenge(username); const oldMainCryptoManager = getMainCryptoManager(this.mainKey, this.version); const content = oldMainCryptoManager.decrypt(this.user.encryptedContent); const oldLoginCryptoManager = oldMainCryptoManager.getLoginCryptoManager(); const mainKey = await deriveKey(loginChallenge.salt, password); const mainCryptoManager = getMainCryptoManager(mainKey, this.version); const loginCryptoManager = mainCryptoManager.getLoginCryptoManager(); const encryptedContent = mainCryptoManager.encrypt(content); const response = msgpackEncode({ username, challenge: loginChallenge.challenge, host: URI(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 Authenticator(serverUrl); return await authenticator.getDashboardUrl(this.authToken); } async save(encryptionKey_) { const version = CURRENT_VERSION; const encryptionKey = encryptionKey_ !== null && encryptionKey_ !== void 0 ? encryptionKey_ : new Uint8Array(32); const cryptoManager = new 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(msgpackEncode(content), new Uint8Array([version])), }; return toBase64(msgpackEncode(ret)); } static async restore(accountDataStored_, encryptionKey_) { var _a; await ready; const encryptionKey = encryptionKey_ !== null && encryptionKey_ !== void 0 ? encryptionKey_ : new Uint8Array(32); const accountDataStored = msgpackDecode(fromBase64(accountDataStored_)); const cryptoManager = new StorageCryptoManager(encryptionKey, accountDataStored.version); const accountData = 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 = getMainCryptoManager(this.mainKey, this.version); const content = mainCryptoManager.decrypt(this.user.encryptedContent); return mainCryptoManager.getAccountCryptoManager(content.subarray(0, symmetricKeyLength)); } _getIdentityCryptoManager() { // FIXME: cache this const mainCryptoManager = getMainCryptoManager(this.mainKey, this.version); const content = mainCryptoManager.decrypt(this.user.encryptedContent); return mainCryptoManager.getIdentityCryptoManager(content.subarray(symmetricKeyLength)); } } const defaultCacheOptions = { saveContent: true, }; export class CollectionManager { constructor(etebase) { this.etebase = etebase; this.onlineManager = new CollectionManagerOnline(this.etebase); } async create(colType, meta, content) { const uintcontent = (content instanceof Uint8Array) ? content : fromString(content); const mainCryptoManager = this.etebase._getCryptoManager(); const encryptedCollection = await 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 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 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 = 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); } } export class ItemManager { constructor(etebase, collectionCryptoManager, colUid) { this.collectionCryptoManager = collectionCryptoManager; this.onlineManager = new CollectionItemManagerOnline(etebase, colUid); this.collectionUid = colUid; } async create(meta, content) { const uintcontent = (content instanceof Uint8Array) ? content : fromString(content); const encryptedItem = await 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 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 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 = EncryptedCollectionItem.cacheLoad(cache); return new Item(this.collectionUid, encItem.getCryptoManager(this.collectionCryptoManager), encItem); } } export class CollectionInvitationManager { constructor(etebase) { this.etebase = etebase; this.onlineManager = new 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 = msgpackDecode(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; } } export class CollectionMemberManager { constructor(etebase, _collectionManager, encryptedCollection) { this.etebase = etebase; this.onlineManager = new 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); } } export var OutputFormat; (function (OutputFormat) { OutputFormat[OutputFormat["Uint8Array"] = 0] = "Uint8Array"; OutputFormat[OutputFormat["String"] = 1] = "String"; })(OutputFormat || (OutputFormat = {})); export 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 : 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 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); } } export 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 : 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 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, EncryptedCollectionItem.deserialize(this.encryptedItem.serialize())); } } //# sourceMappingURL=Etebase.js.map