UNPKG

@sentclose/sentc-nodejs

Version:

End-to-end encryption sdk

384 lines (383 loc) 15.8 kB
"use strict"; /** * @author Jörn Heinemann <joernheinemann@gmx.de> * @since 2022/07/16 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.Sentc = void 0; const sentc_node_js_1 = require("@sentclose/sentc_node_js"); const sentc_common_1 = require("@sentclose/sentc-common"); const User_1 = require("./User"); const node_js_fs_storage_1 = require("./node_js_fs_storage"); class Sentc { static async getStore() { var _a, _b; //only init when needed if (this.init_storage) { //dont init again return this.storage; } if (!((_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.storage) === null || _b === void 0 ? void 0 : _b.getStorage)) { this.storage = new node_js_fs_storage_1.FileSystemStorage(); await this.storage.init(); } else { this.storage = await this.options.storage.getStorage(); } this.init_storage = true; return this.storage; } /** * Initialize the client. * * This only works in a browser environment. * If using ssr, exe init only in the client, not on the server * * load the wasm file (from the app options url or the cdn url) */ static async init(options) { var _a, _b; if (this.init_client) { try { return await this.getActualUser(); } catch (e) { //the user was not logged in, but the client was init return; } } const base_url = (_a = options === null || options === void 0 ? void 0 : options.base_url) !== null && _a !== void 0 ? _a : "https://api.sentc.com"; const refresh = (_b = options === null || options === void 0 ? void 0 : options.refresh) !== null && _b !== void 0 ? _b : { endpoint: 2 /* REFRESH_ENDPOINT.api */, endpoint_url: base_url + "/api/v1/refresh" }; Sentc.options = { base_url, app_token: options === null || options === void 0 ? void 0 : options.app_token, storage: options === null || options === void 0 ? void 0 : options.storage, refresh, file_part_url: options === null || options === void 0 ? void 0 : options.file_part_url }; try { const [user, username] = await this.getActualUser(false, true); if ((refresh === null || refresh === void 0 ? void 0 : refresh.endpoint) === 2 /* REFRESH_ENDPOINT.api */) { //if refresh over api, then do the init const out = await (0, sentc_node_js_1.initUser)(options.base_url, options.app_token, user.user_data.jwt, user.user_data.refresh_token); //save the invites if we fetched them from init request user.user_data.jwt = out.jwt; const group_invites = []; for (let i = 0; i < out.invites.length; i++) { const invite = out.invites[i]; group_invites.push({ group_id: invite.groupId, time: +invite.time }); } user.group_invites = group_invites; } else { //if refresh over cookie -> do normal refresh jwt await user.getJwt(); } const storage = await this.getStore(); //save the user data with the new jwt await storage.set("user_data" /* USER_KEY_STORAGE_NAMES.userData */ + "_id_" + username, user.user_data); this.init_client = true; return user; } catch (e) { //user was not logged in -> do nothing this.init_client = true; } } /** * Do a request to the sentc api to check if the user identifier is still available. * * true => user identifier is free * * @param userIdentifier */ static checkUserIdentifierAvailable(userIdentifier) { return (0, sentc_node_js_1.checkUserIdentifierAvailable)(Sentc.options.base_url, Sentc.options.app_token, userIdentifier); } /** * Prepare the server input for the sentc api to check if an identifier is available * * This function won't do a request * * @param userIdentifier */ static prepareCheckUserIdentifierAvailable(userIdentifier) { if (userIdentifier === "") { return false; } return (0, sentc_node_js_1.prepareCheckUserIdentifierAvailable)(userIdentifier); } /** * Checks the server output after the request. * * This is only needed when not using @see checkUserIdentifierAvailable * * @param serverOutput */ static doneCheckUserIdentifierAvailable(serverOutput) { return (0, sentc_node_js_1.doneCheckUserIdentifierAvailable)(serverOutput); } static generateRegisterData() { const out = (0, sentc_node_js_1.generateUserRegisterData)(); return [ out.identifier, out.password ]; } /** * Generates the register input for the api. * * It can be used in an external backend * * @param userIdentifier * @param password */ static prepareRegister(userIdentifier, password) { return (0, sentc_node_js_1.prepareRegister)(userIdentifier, password); } /** * Validates the register output from the api when using prepare register function * * @param serverOutput */ static doneRegister(serverOutput) { return (0, sentc_node_js_1.doneRegister)(serverOutput); } /** * Register a new user. * * @param userIdentifier * @param password * @throws Error * - if username exists * - request error */ static register(userIdentifier, password) { if (userIdentifier === "" || password === "") { return false; } return (0, sentc_node_js_1.register)(Sentc.options.base_url, Sentc.options.app_token, userIdentifier, password); } static prepareRegisterDeviceStart(device_identifier, password) { return (0, sentc_node_js_1.prepareRegisterDeviceStart)(device_identifier, password); } static doneRegisterDeviceStart(server_output) { return (0, sentc_node_js_1.doneRegisterDeviceStart)(server_output); } static registerDeviceStart(device_identifier, password) { if (device_identifier === "" || password === "") { return false; } return (0, sentc_node_js_1.registerDeviceStart)(Sentc.options.base_url, Sentc.options.app_token, device_identifier, password); } /** * Log the user in * * Store all user data in the storage (e.g., Indexeddb) * * For a refresh token flow -> send the refresh token to your server and save it in an http only strict cookie * Then the user is safe for xss and csrf attacks * * when Either UserMfaLogin is returned, then the user must enter the mfa token. * Use the function Sentc.mfaLogin() to do the totp login or Sentc.mfaRecoveryLogin() to log in with a recover key */ static async login(deviceIdentifier, password, force = false) { var _a, _b; const out = await (0, sentc_node_js_1.login)(Sentc.options.base_url, Sentc.options.app_token, deviceIdentifier, password); const mfa_master_key = (_a = out === null || out === void 0 ? void 0 : out.mfa) === null || _a === void 0 ? void 0 : _a.masterKey; const mfa_auth_key = (_b = out === null || out === void 0 ? void 0 : out.mfa) === null || _b === void 0 ? void 0 : _b.authKey; if (mfa_master_key !== undefined && mfa_auth_key !== undefined) { if (force) { throw (0, sentc_common_1.create_error)("client_10000", "User enabled mfa and this must be handled."); } //mfa action needed return { kind: "mfa", u: { deviceIdentifier, mfa_auth_key, mfa_master_key } }; } //at this point the user disabled mfa const user = await (0, User_1.getUser)(deviceIdentifier, out.userData, false); if (force) { return user; } return { kind: "user", u: user }; } static async mfaLogin(token, login_data) { const out = await (0, sentc_node_js_1.mfaLogin)(Sentc.options.base_url, Sentc.options.app_token, login_data.mfa_master_key, login_data.mfa_auth_key, login_data.deviceIdentifier, token, false); return (0, User_1.getUser)(login_data.deviceIdentifier, out, true); } static async mfaRecoveryLogin(recovery_token, login_data) { const out = await (0, sentc_node_js_1.mfaLogin)(Sentc.options.base_url, Sentc.options.app_token, login_data.mfa_master_key, login_data.mfa_auth_key, login_data.deviceIdentifier, recovery_token, true); return (0, User_1.getUser)(login_data.deviceIdentifier, out, true); } /** * get a new jwt when the old one is expired * * The check is done automatically when making a sentc api request * * It can be refreshed directly with the sdk, a request to another backend with a cookie or with an own function * * @param old_jwt * @param refresh_token */ static refreshJwt(old_jwt, refresh_token) { const options = this.options.refresh; if (options.endpoint === 2 /* REFRESH_ENDPOINT.api */) { //make the req directly to the api, via wasm return (0, sentc_node_js_1.refreshJwt)(this.options.base_url, this.options.app_token, old_jwt, refresh_token); } //a refresh token is not needed for the other options because the dev is responsible to send the refresh token // e.g., via http only cookie if (options.endpoint === 0 /* REFRESH_ENDPOINT.cookie */) { const headers = new Headers(); headers.append("Authorization", "Bearer " + old_jwt); //make the req without a body because the token sits in the cookie return fetch(options.endpoint_url, { method: "GET", credentials: "include", headers }).then((res) => { return res.text(); }); } if (options.endpoint === 1 /* REFRESH_ENDPOINT.cookie_fn */) { //make the req via the cookie fn, where the dev can define an own refresh flow return options.endpoint_fn(old_jwt); } throw new Error("No refresh option found"); } static getUserPublicKey(user_id) { return this.getUserPublicKeyData(this.options.base_url, this.options.app_token, user_id); } static getUserVerifyKey(user_id, key_id) { return this.getUserVerifyKeyData(this.options.base_url, this.options.app_token, user_id, key_id); } static getGroupPublicKey(group_id) { return this.getGroupPublicKeyData(this.options.base_url, this.options.app_token, group_id); } static verifyUserPublicKey(user_id, public_key, force = false) { return this.verifyUsersPublicKey(user_id, public_key, force); } static async getActualUser(jwt = false, username = false) { const storage = await this.getStore(); const actualUser = await storage.getItem("actual_user" /* USER_KEY_STORAGE_NAMES.actualUser */); if (!actualUser) { throw new Error("No actual user found"); } const user = await this.getUser(actualUser); if (!user) { throw new Error("The actual user data was not found"); } if (jwt) { await user.getJwt(); return user; } if (username) { return [user, actualUser]; } return user; } /** * Get any user matched by the user identifier. * * The user data is stored in the indexeddb (standard) or in memory * * @param userIdentifier */ static async getUser(userIdentifier) { const storage = await this.getStore(); const user = await storage.getItem("user_data" /* USER_KEY_STORAGE_NAMES.userData */ + "_id_" + userIdentifier); if (!user) { return false; } return new User_1.User(this.options.base_url, this.options.app_token, user, userIdentifier); } /** * The same as getUserPublicData but only fetched the public key * * @param base_url * @param app_token * @param user_id */ static async getUserPublicKeyData(base_url, app_token, user_id) { const storage = await this.getStore(); const store_key = "user_public_key" /* USER_KEY_STORAGE_NAMES.userPublicKey */ + "_id_" + user_id; const user = await storage.getItem(store_key); if (user) { return user; } const fetched_key = await (0, sentc_node_js_1.userFetchPublicKey)(base_url, app_token, user_id); const public_key = fetched_key.publicKey; const public_key_id = fetched_key.publicKeyId; const public_key_sig_key_id = fetched_key.publicKeySigKeyId; const returns = { public_key, public_key_id, public_key_sig_key_id, verified: false }; await storage.set(store_key, returns); return returns; } /** * The same as getUserPublicData but only fetched the verify-key * * @param base_url * @param app_token * @param user_id * @param verify_key_id */ static async getUserVerifyKeyData(base_url, app_token, user_id, verify_key_id) { const storage = await this.getStore(); const store_key = "user_verify_key" /* USER_KEY_STORAGE_NAMES.userVerifyKey */ + "_id_" + user_id + "_key_id_" + verify_key_id; const user = await storage.getItem(store_key); if (user) { return user; } const fetched_key = await (0, sentc_node_js_1.userFetchVerifyKey)(base_url, app_token, user_id, verify_key_id); await storage.set(store_key, fetched_key); return fetched_key; } static async getGroupPublicKeyData(base_url, app_token, group_id) { const storage = await this.getStore(); const store_key = "group_public_key" /* USER_KEY_STORAGE_NAMES.groupPublicKey */ + "_id_" + group_id; const group = await storage.getItem(store_key); if (group) { return group; } const fetched_key = await (0, sentc_node_js_1.groupGetPublicKeyData)(base_url, app_token, group_id); const key = fetched_key.publicKey; const id = fetched_key.publicKeyId; const returns = { key, id }; await storage.set(store_key, returns); return returns; } static async verifyUsersPublicKey(user_id, public_key, force = false) { if (public_key.verified && !force) { return true; } if (!public_key.public_key_sig_key_id) { return false; } const verify_key = await this.getUserVerifyKey(user_id, public_key.public_key_sig_key_id); const verify = (0, sentc_node_js_1.userVerifyUserPublicKey)(verify_key, public_key.public_key); public_key.verified = verify; //store the new value const storage = await this.getStore(); const store_key = "user_public_key" /* USER_KEY_STORAGE_NAMES.userPublicKey */ + "_id_" + user_id; await storage.set(store_key, public_key); return verify; } } exports.Sentc = Sentc; Sentc.init_client = false; Sentc.init_storage = false; //@ts-ignore Sentc.options = {};