@sentclose/sentc-nodejs
Version:
End-to-end encryption sdk
384 lines (383 loc) • 15.8 kB
JavaScript
"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 = {};