UNPKG

@sentclose/sentc-nodejs

Version:

End-to-end encryption sdk

588 lines (587 loc) 28.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.User = exports.getUser = void 0; const AbstractAsymCrypto_1 = require("./crypto/AbstractAsymCrypto"); const sentc_node_js_1 = require("@sentclose/sentc_node_js"); const Sentc_1 = require("./Sentc"); const Group_1 = require("./Group"); const file_1 = require("./file"); const sentc_common_1 = require("@sentclose/sentc-common"); const path = require("path"); async function setUserStorageData(user_data, deviceIdentifier) { const storage = await Sentc_1.Sentc.getStore(); const store_user_data = user_data; if (Sentc_1.Sentc.options.refresh.endpoint !== 2 /* REFRESH_ENDPOINT.api */) { //if the refresh token should not be stored on the client -> invalidates the stored refresh token //but return the refresh token with the rest of the user data store_user_data.refresh_token = ""; } return storage.set("user_data" /* USER_KEY_STORAGE_NAMES.userData */ + "_id_" + deviceIdentifier, store_user_data); } async function getUser(deviceIdentifier, data, mfa) { //Only fetch the older keys when needed; this is not like a group where all keys must be available //user key map const key_map = new Map(); const user_keys = []; for (let i = 0; i < data.userKeys.length; i++) { const key = data.userKeys[i]; key_map.set(key.groupKeyId, i); user_keys.push({ group_key_id: key.groupKeyId, verify_key: key.verifyKey, exported_verify_key: key.exportedVerifyKey, time: +key.time, sign_key: key.signKey, public_key: key.publicKey, exported_public_key_sig_key_id: key.exportedPublicKeySigKeyId, exported_public_key: key.exportedPublicKey, group_key: key.groupKey, private_key: key.privateKey }); } const newest_key_id = data.userKeys[0].groupKeyId; const user_data = { newest_key_id, key_map, user_keys, hmac_keys: [], mfa, jwt: data.jwt, user_id: data.userId, device_id: data.deviceId, refresh_token: data.refreshToken, device: { exported_verify_key: data.keys.exportedVerifyKey, verify_key: data.keys.verifyKey, exported_public_key: data.keys.exportedPublicKey, public_key: data.keys.publicKey, sign_key: data.keys.signKey, private_key: data.keys.privateKey } }; const store_user_data = user_data; if (Sentc_1.Sentc.options.refresh.endpoint !== 2 /* REFRESH_ENDPOINT.api */) { //if the refresh token should not be stored on the client -> invalidates the stored refresh token //but return the refresh token with the rest of the user data store_user_data.refresh_token = ""; } const user = new User(Sentc_1.Sentc.options.base_url, Sentc_1.Sentc.options.app_token, user_data, deviceIdentifier); //decrypt the hmac key const hmacKeys = []; for (let i = 0; i < data.hmacKeys.length; i++) { const k = data.hmacKeys[i]; hmacKeys.push({ key_data: k.keyData, group_key_id: k.groupKeyId }); } const decrypted_hmac_keys = await user.decryptHmacKeys(hmacKeys); user.user_data.hmac_keys = decrypted_hmac_keys; store_user_data.hmac_keys = decrypted_hmac_keys; //save user data in indexeddb const storage = await Sentc_1.Sentc.getStore(); await Promise.all([ storage.set("user_data" /* USER_KEY_STORAGE_NAMES.userData */ + "_id_" + deviceIdentifier, store_user_data), storage.set("actual_user" /* USER_KEY_STORAGE_NAMES.actualUser */, deviceIdentifier), //save always the newest public key storage.set("user_public_key" /* USER_KEY_STORAGE_NAMES.userPublicKey */ + "_id_" + user_data.user_id, { public_key: user_data.user_keys[0].exported_public_key, public_key_id: user_data.user_keys[0].group_key_id, public_key_sig_key_id: user_data.user_keys[0].exported_public_key_sig_key_id, verified: false }), storage.set("user_verify_key" /* USER_KEY_STORAGE_NAMES.userVerifyKey */ + "_id_" + user_data.user_id + "_key_id_" + user_data.user_keys[0].group_key_id, user_data.user_keys[0].exported_verify_key) ]); return user; } exports.getUser = getUser; class User extends AbstractAsymCrypto_1.AbstractAsymCrypto { constructor(base_url, app_token, user_data, userIdentifier, group_invites = []) { super(base_url, app_token); this.user_data = user_data; this.userIdentifier = userIdentifier; this.group_invites = group_invites; } async getUserKeys(key_id, first = false) { let index = this.user_data.key_map.get(key_id); if (index === undefined) { //try to fetch the keys from the server await this.fetchUserKey(key_id, first); index = this.user_data.key_map.get(key_id); if (index === undefined) { //key not found throw new Error("Key not found"); } } const key = this.user_data.user_keys[index]; if (!key) { //key not found throw new Error("Key not found"); } return key; } getUserKeysSync(key_id) { const index = this.user_data.key_map.get(key_id); if (index === undefined) { throw new Error("Key not found"); } const key = this.user_data.user_keys[index]; if (!key) { //key not found throw new Error("Key not found"); } return key; } async getUserSymKey(key_id) { const key = await this.getUserKeys(key_id); return key.group_key; } async getPrivateKey(key_id) { const key = await this.getUserKeys(key_id); return key.private_key; } getPrivateKeySync(key_id) { const key = this.getUserKeysSync(key_id); return key.private_key; } getPublicKey(reply_id) { return Sentc_1.Sentc.getUserPublicKeyData(this.base_url, this.app_token, reply_id); } getNewestHmacKey() { return this.user_data.hmac_keys[0]; } getNewestKey() { let index = this.user_data.key_map.get(this.user_data.newest_key_id); if (index === undefined) { index = 0; } return this.user_data.user_keys[index]; } getNewestPublicKey() { return this.getNewestKey().public_key; } getNewestSignKey() { return this.getNewestKey().sign_key; } getSignKey() { return Promise.resolve(this.getNewestSignKey()); } getSignKeySync() { return this.getNewestSignKey(); } enabledMfa() { return this.user_data.mfa; } async decryptHmacKeys(fetchedKeys) { const keys = []; for (let i = 0; i < fetchedKeys.length; i++) { const fetched_key = fetchedKeys[i]; // eslint-disable-next-line no-await-in-loop const group_key = await this.getUserSymKey(fetched_key.group_key_id); const decrypted_hmac_key = (0, sentc_node_js_1.groupDecryptHmacKey)(group_key, fetched_key.key_data); keys.push(decrypted_hmac_key); } return keys; } async fetchUserKey(key_id, first = false) { const jwt = await this.getJwt(); const fetched_keys = await (0, sentc_node_js_1.fetchUserKey)(this.base_url, this.app_token, jwt, key_id, this.user_data.device.private_key); const user_keys = { exported_verify_key: fetched_keys.exportedVerifyKey, group_key_id: fetched_keys.groupKeyId, verify_key: fetched_keys.verifyKey, time: +fetched_keys.time, sign_key: fetched_keys.signKey, public_key: fetched_keys.publicKey, exported_public_key_sig_key_id: fetched_keys.exportedPublicKeySigKeyId, exported_public_key: fetched_keys.exportedPublicKey, group_key: fetched_keys.groupKey, private_key: fetched_keys.privateKey }; const index = this.user_data.user_keys.length; this.user_data.user_keys.push(user_keys); this.user_data.key_map.set(user_keys.group_key_id, index); if (first) { this.user_data.newest_key_id = user_keys.group_key_id; } return setUserStorageData(this.user_data, this.userIdentifier); } async getJwt() { const jwt_data = (0, sentc_node_js_1.decodeJwt)(this.user_data.jwt); const exp = jwt_data.exp; if (exp <= Date.now() / 1000 + 30) { //refresh even when the jwt is valid for 30 sec //update the user data to safe the updated values, we don't need the class here this.user_data.jwt = await Sentc_1.Sentc.refreshJwt(this.user_data.jwt, this.user_data.refresh_token); //save the user data with the new jwt await setUserStorageData(this.user_data, this.userIdentifier); } return this.user_data.jwt; } getFreshJwt(username, password, mfa_token, mfa_recovery) { return (0, sentc_node_js_1.getFreshJwt)(this.base_url, this.app_token, username, password, mfa_token, mfa_recovery); } async updateUser(newIdentifier) { const jwt = await this.getJwt(); return (0, sentc_node_js_1.updateUser)(this.base_url, this.app_token, jwt, newIdentifier); } async registerRawOtp(password, mfa_token, mfa_recovery) { const fresh_jwt = await this.getFreshJwt(this.userIdentifier, password, mfa_token, mfa_recovery); const out = await (0, sentc_node_js_1.registerRawOtp)(this.base_url, this.app_token, fresh_jwt); this.user_data.mfa = true; await setUserStorageData(this.user_data, this.userIdentifier); return out; } async registerOtp(issuer, audience, password, mfa_token, mfa_recovery) { const fresh_jwt = await this.getFreshJwt(this.userIdentifier, password, mfa_token, mfa_recovery); const out = await (0, sentc_node_js_1.registerOtp)(this.base_url, this.app_token, fresh_jwt, issuer, audience); this.user_data.mfa = true; await setUserStorageData(this.user_data, this.userIdentifier); return [out.url, out.recover]; } async getOtpRecoverKeys(password, mfa_token, mfa_recovery) { const fresh_jwt = await this.getFreshJwt(this.userIdentifier, password, mfa_token, mfa_recovery); const out = await (0, sentc_node_js_1.getOtpRecoverKeys)(this.base_url, this.app_token, fresh_jwt); return out.keys; } async resetRawOtp(password, mfa_token, mfa_recovery) { const fresh_jwt = await this.getFreshJwt(this.userIdentifier, password, mfa_token, mfa_recovery); return (0, sentc_node_js_1.resetRawOtp)(this.base_url, this.app_token, fresh_jwt); } async resetOtp(issuer, audience, password, mfa_token, mfa_recovery) { const fresh_jwt = await this.getFreshJwt(this.userIdentifier, password, mfa_token, mfa_recovery); const out = await (0, sentc_node_js_1.resetOtp)(this.base_url, this.app_token, fresh_jwt, issuer, audience); return [out.url, out.recover]; } async disableOtp(password, mfa_token, mfa_recovery) { const fresh_jwt = await this.getFreshJwt(this.userIdentifier, password, mfa_token, mfa_recovery); await (0, sentc_node_js_1.disableOtp)(this.base_url, this.app_token, fresh_jwt); this.user_data.mfa = false; return setUserStorageData(this.user_data, this.userIdentifier); } async resetPassword(newPassword) { //check if the user is logged in with a valid jwt and got the private keys const jwt = await this.getJwt(); const decryptedPrivateKey = this.user_data.device.private_key; const decryptedSignKey = this.user_data.device.sign_key; return (0, sentc_node_js_1.resetPassword)(this.base_url, this.app_token, jwt, newPassword, decryptedPrivateKey, decryptedSignKey); } changePassword(oldPassword, newPassword, mfa_token, mfa_recovery) { if (this.user_data.mfa && !mfa_token) { throw (0, sentc_common_1.create_error)("client_10000", "The user enabled mfa. To change the password, the user must also enter the mfa token"); } return (0, sentc_node_js_1.changePassword)(this.base_url, this.app_token, this.userIdentifier, oldPassword, newPassword, mfa_token, mfa_recovery); } async logOut() { const storage = await Sentc_1.Sentc.getStore(); return storage.delete("user_data" /* USER_KEY_STORAGE_NAMES.userData */ + "_id_" + this.userIdentifier); } async deleteUser(password, mfa_token, mfa_recovery) { if (this.user_data.mfa && !mfa_token) { throw (0, sentc_common_1.create_error)("client_10000", "The user enabled mfa. To delete the user, the user must also enter the mfa token"); } const fresh_jwt = await this.getFreshJwt(this.userIdentifier, password, mfa_token, mfa_recovery); await (0, sentc_node_js_1.deleteUser)(this.base_url, this.app_token, fresh_jwt); return this.logOut(); } async deleteDevice(password, device_id, mfa_token, mfa_recovery) { if (this.user_data.mfa && !mfa_token) { throw (0, sentc_common_1.create_error)("client_10000", "The user enabled mfa. To delete a device, the user must also enter the mfa token"); } const fresh_jwt = await this.getFreshJwt(this.userIdentifier, password, mfa_token, mfa_recovery); await (0, sentc_node_js_1.deleteDevice)(this.base_url, this.app_token, fresh_jwt, device_id); if (device_id === this.user_data.device_id) { //only log the device out if it is the actual used device return this.logOut(); } } //__________________________________________________________________________________________________________________ prepareRegisterDevice(server_output, page = 0) { const key_count = this.user_data.user_keys.length; const [key_string] = (0, Group_1.prepareKeys)(this.user_data.user_keys, page); return (0, sentc_node_js_1.prepareRegisterDevice)(server_output, key_string, key_count); } async registerDevice(server_output) { const key_count = this.user_data.user_keys.length; const [key_string] = (0, Group_1.prepareKeys)(this.user_data.user_keys); const jwt = await this.getJwt(); const out = await (0, sentc_node_js_1.registerDevice)(this.base_url, this.app_token, jwt, server_output, key_count, key_string); const session_id = out.sessionId; const public_key = out.exportedPublicKey; if (session_id === "") { return; } let next_page = true; let i = 1; const p = []; while (next_page) { const next_keys = (0, Group_1.prepareKeys)(this.user_data.user_keys, i); next_page = next_keys[1]; p.push((0, sentc_node_js_1.userDeviceKeySessionUpload)(this.base_url, this.app_token, jwt, session_id, public_key, next_keys[0])); i++; } return Promise.allSettled(p); } async getDevices(last_fetched_item = null) { var _a, _b; const jwt = await this.getJwt(); const last_fetched_time = (_a = last_fetched_item === null || last_fetched_item === void 0 ? void 0 : last_fetched_item.time.toString()) !== null && _a !== void 0 ? _a : "0"; const last_id = (_b = last_fetched_item === null || last_fetched_item === void 0 ? void 0 : last_fetched_item.device_id) !== null && _b !== void 0 ? _b : "none"; const out = await (0, sentc_node_js_1.getUserDevices)(this.base_url, this.app_token, jwt, last_fetched_time, last_id); const arr = []; for (let i = 0; i < out.length; i++) { const device = out[i]; arr.push({ device_id: device.deviceId, time: +device.time, device_identifier: device.deviceIdentifier }); } return arr; } async createSafetyNumber(user_to_compare) { let verify_key_2; if (user_to_compare) { verify_key_2 = await Sentc_1.Sentc.getUserVerifyKeyData(this.base_url, this.app_token, user_to_compare.user_id, user_to_compare.verify_key_id); } return (0, sentc_node_js_1.userCreateSafetyNumber)(this.getNewestKey().exported_verify_key, this.user_data.user_id, verify_key_2, user_to_compare === null || user_to_compare === void 0 ? void 0 : user_to_compare.user_id); } //__________________________________________________________________________________________________________________ async keyRotation() { const jwt = await this.getJwt(); const key_id = await (0, sentc_node_js_1.userKeyRotation)(this.base_url, this.app_token, jwt, this.user_data.device.public_key, this.getNewestKey().group_key); return this.fetchUserKey(key_id, true); } async finishKeyRotation() { const jwt = await this.getJwt(); let keys = []; const out = await (0, sentc_node_js_1.userPreDoneKeyRotation)(this.base_url, this.app_token, jwt); for (let i = 0; i < out.length; i++) { const key = out[i]; keys.push({ encrypted_eph_key_key_id: key.encryptedEphKeyKeyId, new_group_key_id: key.newGroupKeyId, pre_group_key_id: key.preGroupKeyId, server_output: key.serverOutput }); } let next_round = false; let rounds_left = 10; const public_key = this.user_data.device.public_key; const private_key = this.user_data.device.private_key; do { const left_keys = []; for (let i = 0; i < keys.length; i++) { const key = keys[i]; let pre_key; try { // eslint-disable-next-line no-await-in-loop pre_key = await this.getUserKeys(key.pre_group_key_id); } catch (e) { //key isn't found, try next round } if (pre_key === undefined) { left_keys.push(key); continue; } // eslint-disable-next-line no-await-in-loop await (0, sentc_node_js_1.userFinishKeyRotation)(this.base_url, this.app_token, jwt, key.server_output, pre_key.group_key, public_key, private_key); // eslint-disable-next-line no-await-in-loop await this.getUserKeys(key.new_group_key_id, true); } rounds_left--; if (left_keys.length > 0) { keys = []; //push the not found keys into the key array, maybe the pre-group keys are in the next round keys.push(...left_keys); next_round = true; } else { next_round = false; } } while (next_round && rounds_left > 0); } //__________________________________________________________________________________________________________________ async getGroups(last_fetched_item = null) { var _a, _b; const jwt = await this.getJwt(); const last_fetched_time = (_a = last_fetched_item === null || last_fetched_item === void 0 ? void 0 : last_fetched_item.time.toString()) !== null && _a !== void 0 ? _a : "0"; const last_id = (_b = last_fetched_item === null || last_fetched_item === void 0 ? void 0 : last_fetched_item.group_id) !== null && _b !== void 0 ? _b : "none"; const out = await (0, sentc_node_js_1.groupGetGroupsForUser)(this.base_url, this.app_token, jwt, last_fetched_time, last_id); const arr = []; for (let i = 0; i < out.length; i++) { const group = out[i]; arr.push({ group_id: group.groupId, time: +group.time, rank: group.rank, parent: group.parent, joined_time: +group.joinedTime }); } return arr; } async getGroupInvites(last_fetched_item = null) { var _a, _b; const jwt = await this.getJwt(); const last_fetched_time = (_a = last_fetched_item === null || last_fetched_item === void 0 ? void 0 : last_fetched_item.time.toString()) !== null && _a !== void 0 ? _a : "0"; const last_id = (_b = last_fetched_item === null || last_fetched_item === void 0 ? void 0 : last_fetched_item.group_id) !== null && _b !== void 0 ? _b : "none"; const out = await (0, sentc_node_js_1.groupGetInvitesForUser)(this.base_url, this.app_token, jwt, last_fetched_time, last_id); const arr = []; for (let i = 0; i < out.length; i++) { const group = out[i]; arr.push({ group_id: group.groupId, time: +group.time }); } return arr; } async acceptGroupInvite(group_id) { const jwt = await this.getJwt(); return (0, sentc_node_js_1.groupAcceptInvite)(this.base_url, this.app_token, jwt, group_id); } async rejectGroupInvite(group_id) { const jwt = await this.getJwt(); return (0, sentc_node_js_1.groupRejectInvite)(this.base_url, this.app_token, jwt, group_id); } //join req async groupJoinRequest(group_id) { const jwt = await this.getJwt(); return (0, sentc_node_js_1.groupJoinReq)(this.base_url, this.app_token, jwt, group_id, ""); } async sentJoinReq(last_fetched_item = null) { var _a, _b; const jwt = await this.getJwt(); const last_fetched_time = (_a = last_fetched_item === null || last_fetched_item === void 0 ? void 0 : last_fetched_item.time.toString()) !== null && _a !== void 0 ? _a : "0"; const last_id = (_b = last_fetched_item === null || last_fetched_item === void 0 ? void 0 : last_fetched_item.group_id) !== null && _b !== void 0 ? _b : "none"; const out = await (0, sentc_node_js_1.groupGetSentJoinReqUser)(this.base_url, this.app_token, jwt, last_fetched_time, last_id); const arr = []; for (let i = 0; i < out.length; i++) { const group = out[i]; arr.push({ group_id: group.groupId, time: +group.time }); } return arr; } async deleteJoinReq(id) { const jwt = await this.getJwt(); return (0, sentc_node_js_1.groupDeleteSentJoinReqUser)(this.base_url, this.app_token, jwt, id); } //__________________________________________________________________________________________________________________ prepareGroupCreate(sign = false) { let sign_key; if (sign) { sign_key = this.getNewestSignKey(); } //important use the public key, not the exported public key here! return (0, sentc_node_js_1.groupPrepareCreateGroup)(this.getNewestPublicKey(), sign_key, this.user_data.user_id); } async createGroup(sign = false) { const jwt = await this.getJwt(); let sign_key; if (sign) { sign_key = this.getNewestSignKey(); } return (0, sentc_node_js_1.groupCreateGroup)(this.base_url, this.app_token, jwt, this.getNewestPublicKey(), undefined, sign_key, this.user_data.user_id); } getGroup(group_id, group_as_member, verify = 0) { return (0, Group_1.getGroup)(group_id, this.base_url, this.app_token, this, false, group_as_member, verify); } async prepareRegisterFile(file, reply_id = "") { const other_user = (reply_id !== "") ? reply_id : undefined; reply_id = (reply_id !== "") ? reply_id : this.user_data.user_id; const [key, encrypted_key] = await this.generateNonRegisteredKey(reply_id); const uploader = new file_1.Uploader(this.base_url, this.app_token, this, undefined, other_user); const [server_input, encrypted_file_name] = uploader.prepareFileRegister(file, key.key, encrypted_key, key.master_key_id); return { server_input, encrypted_file_name, key, master_key_id: key.master_key_id }; } /** * Validates the sentc file register output * Returns the file id * * @param server_output */ doneFileRegister(server_output) { const uploader = new file_1.Uploader(this.base_url, this.app_token, this); return uploader.doneFileRegister(server_output); } uploadFile(fileHandle, fileSize, content_key, session_id, sign = false, upload_callback) { const uploader = new file_1.Uploader(this.base_url, this.app_token, this, undefined, undefined, upload_callback); return uploader.checkFileUpload(fileHandle, fileSize, content_key.key, session_id, sign); } async getFileMetaInfo(file_id, downloader, verify_key) { //1. get the file info const file_meta = await downloader.downloadFileMetaInformation(file_id); //2. get the content key which was used to encrypt the file const key = await this.getNonRegisteredKey(file_meta.master_key_id, file_meta.encrypted_key); //3. get the file name if any if (file_meta.encrypted_file_name && file_meta.encrypted_file_name !== "") { file_meta.file_name = key.decryptString(file_meta.encrypted_file_name, verify_key); } return [file_meta, key]; } downloadFileMetaInfo(file_id, verify_key) { const downloader = new file_1.Downloader(this.base_url, this.app_token, this); return this.getFileMetaInfo(file_id, downloader, verify_key); } downloadFileWithMetaInfo(fileHandle, key, file_meta, verify_key, updateProgressCb) { const downloader = new file_1.Downloader(this.base_url, this.app_token, this); return downloader.downloadFileParts(fileHandle, file_meta.part_list, key.key, updateProgressCb, verify_key); } async createFile(file, file_name, sign = false, reply_id = "", upload_callback) { const other_user = (reply_id !== "") ? reply_id : undefined; reply_id = (reply_id !== "") ? reply_id : this.user_data.user_id; //1st register a new key for this file const [key, encrypted_key] = await this.generateNonRegisteredKey(reply_id); //2nd encrypt and upload the file, use the created key const uploader = new file_1.Uploader(this.base_url, this.app_token, this, undefined, other_user, upload_callback); const [file_id, encrypted_file_name] = await uploader.uploadFile(file, file_name, key.key, encrypted_key, key.master_key_id, sign); return { file_id, master_key_id: key.master_key_id, encrypted_file_name }; } async createFileWithPath(path, sign = false, reply_id = "", upload_callback) { const other_user = (reply_id !== "") ? reply_id : undefined; reply_id = (reply_id !== "") ? reply_id : this.user_data.user_id; //1st register a new key for this file const [key, encrypted_key] = await this.generateNonRegisteredKey(reply_id); //2nd encrypt and upload the file, use the created key const uploader = new file_1.Uploader(this.base_url, this.app_token, this, undefined, other_user, upload_callback); const [file_id, encrypted_file_name] = await uploader.uploadFileWithPath(path, key.key, encrypted_key, key.master_key_id, sign); return { file_id, master_key_id: key.master_key_id, encrypted_file_name }; } async downloadFile(file_path, file_id, verify_key, updateProgressCb) { const downloader = new file_1.Downloader(this.base_url, this.app_token, this); const [file_meta, key] = await this.getFileMetaInfo(file_id, downloader, verify_key); const file_name = await (0, file_1.findAvailableFileName)(path.join(file_path, file_meta.file_name)); if (!file_name) { throw new Error("Could not find a file name"); } await downloader.downloadFilePartsWithPath(file_name, file_meta.part_list, key.key, updateProgressCb, verify_key); return [ file_meta, key ]; } async updateFileName(file_id, content_key, file_name) { const jwt = await this.getJwt(); return (0, sentc_node_js_1.fileFileNameUpdate)(this.base_url, this.app_token, jwt, file_id, content_key.key, file_name); } async deleteFile(file_id) { const jwt = await this.getJwt(); return (0, sentc_node_js_1.fileDeleteFile)(this.base_url, this.app_token, jwt, file_id); } } exports.User = User;