"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.IccRecoveryXApi = exports.RecoveryDataUseFailureReason = void 0;
const RSA_1 = require("./crypto/RSA");
const utils_1 = require("./utils");
const RecoveryData_1 = require("../icc-api/model/internal/RecoveryData");
var RecoveryDataEncryption_1 = require("./crypto/RecoveryDataEncryption");
Object.defineProperty(exports, "RecoveryDataUseFailureReason", { enumerable: true, get: function () { return RecoveryDataEncryption_1.RecoveryDataUseFailureReason; } });
class IccRecoveryXApi {
    constructor(baseRecoveryApi, recoveryDataEncryption, keyManager, dataOwnerApi, primitives, exchangeData) {
        this.baseRecoveryApi = baseRecoveryApi;
        this.recoveryDataEncryption = recoveryDataEncryption;
        this.keyManager = keyManager;
        this.dataOwnerApi = dataOwnerApi;
        this.primitives = primitives;
        this.exchangeData = exchangeData;
    }
    /**
     * Create recovery data for the logged user and stores it encrypted on the iCure server. This allows the user that created
     * it to recover all the currently available keypairs at a later time by just providing the string returned by this method to the
     * {@link recoverKeyPairs} method.
     *
     * You can also provide an expiration time for the recovery data (through `options.lifetimeSeconds`). If you do so, the recovery dat
     * a will be deleted automatically after that amount seconds has passed. If you don't provide an expiration time, the recovery data
     * will be available until it is explicitly deleted.
     *
     * This could be used to:
     * - Provide some sort of one-use "recovery codes" to the user, which he can use to recover his keypair if he loses
     *   his device. In this case you should not put any expiration time on the recovery data.
     * - Provide a way for the user to easily "copy" the key from one device to another. In this case you should put a
     *   short expiration time on the recovery data (a few minutes), so that it will automatically be deleted if it is
     *   not used after all.
     *
     * # Important
     *
     * A malicious user that can login as the creator of the recovery data or that can access directly the database
     * containing the recovery data will be able to get the private key of the data owner from the recovery key returned
     * by this method. Therefore, the resulting recovery key must be kept secret, just like a private key.
     *
     * @param options optional parameters:
     * - `includeParentsKeys` if true, the recovery data will also contain any available keypairs for parents data owners.
     * - `lifetimeSeconds` the amount of seconds the recovery data will be available. If not provided, the recovery data will be available until it is
     *   explicitly deleted.
     * @return an hexadecimal string that is the `recoveryKey` which will allow the user to recover his keypair later or
     * from another device. This value must be kept secret from other users. You can use this value with {@link recoverKeyPairs}
     */
    createRecoveryInfoForAvailableKeyPairs() {
        return __awaiter(this, arguments, void 0, function* (options = {}) {
            const selfId = yield this.dataOwnerApi.getCurrentDataOwnerId();
            const allAvailableKeys = yield this.keyManager.getCurrentUserHierarchyAvailableKeypairs();
            let dataOwnersToInclude;
            if (options.includeParentsKeys) {
                dataOwnersToInclude = [allAvailableKeys.self, ...allAvailableKeys.parents];
            }
            else {
                dataOwnersToInclude = [allAvailableKeys.self];
            }
            const keyPairsToSave = {};
            for (const { dataOwnerId, keys } of dataOwnersToInclude) {
                const dataOwner = yield this.dataOwnerApi.getDataOwner(dataOwnerId);
                const sha1Keys = new Set(this.dataOwnerApi.getHexPublicKeysWithSha1Of(dataOwner.dataOwner));
                const sha256Keys = new Set(this.dataOwnerApi.getHexPublicKeysWithSha256Of(dataOwner.dataOwner));
                const pairs = [];
                for (const { pair, verified } of keys) {
                    if (verified) {
                        const pubKeyHex = (0, utils_1.ua2hex)(yield this.primitives.RSA.exportKey(pair.publicKey, 'spki'));
                        if (sha256Keys.has(pubKeyHex)) {
                            pairs.push({ pair, algorithm: RSA_1.ShaVersion.Sha256 });
                        }
                        else if (sha1Keys.has(pubKeyHex)) {
                            pairs.push({ pair, algorithm: RSA_1.ShaVersion.Sha1 });
                        }
                        else {
                            console.warn(`Found stored key ${pubKeyHex} for data owner ${dataOwnerId} which is not saved in the data owner. Ignoring.`);
                        }
                    }
                }
                keyPairsToSave[dataOwnerId] = pairs;
            }
            return yield this.recoveryDataEncryption.createAndSaveKeyPairsRecoveryDataFor(selfId, keyPairsToSave, options.lifetimeSeconds);
        });
    }
    /**
     * Equivalent to {@link KeyPairRecoverer.recoverWithRecoveryKey}
     */
    recoverKeyPairs(recoveryKey, autoDelete) {
        return this.recoveryDataEncryption.getAndDecryptKeyPairsRecoveryData(recoveryKey, autoDelete);
    }
    /**
     * Create recovery data that allows the delegate {@link delegateId} recover the content of exchange data from the
     * current data owner to the delegate.
     *
     * This can be useful in the following situations:
     * - A user lost access to his old keypair and is asking for access back to his data. This is similar to the
     *   give-access-back mechanism, except that instead of having to verify the public key of the delegate as in the
     *   give-access-back, the delegator has to provide the recovery key to the delegate: the delegator has to properly
     *   identify the delegate instead of validating that the public key belongs to the delegate.
     * - A patient is not yet registered in the system and therefore has no keypair, but the doctor wants to already share
     *   some data with them. The doctor can create some placeholder exchange data, encrypted only with his own key
     *   through the {@link IccPatientXApi.forceInitialiseExchangeDataToNewlyInvitedPatient} method, then create recovery data
     *   for it and share the recovery key with the patient. The moment the patient logs in and creates his keypair he
     *   will use the {@link recoverExchangeData} method to "complete" the placeholder exchange data.
     *
     * @param delegateId id of the delegate that needs access to his exchange data from the current data owner. This can't
     * be the id of the current data owner (you should instead recover the keypair).
     * @param options optional parameters:
     * - `lifetimeSeconds` the amount of seconds the recovery data will be available. If not provided, the recovery data will be available until it is
     *   explicitly deleted.
     * @return an hexadecimal string that is the `recoveryKey` which will allow the delegate to gain access to the exchange data.
     * This value must be kept secret from users other than the current data owner and the delegate.
     * You can use this value with {@link recoverExchangeData}
     */
    createExchangeDataRecoveryInfo(delegateId_1) {
        return __awaiter(this, arguments, void 0, function* (delegateId, options = {}) {
            const exchangeDataToDelegate = yield this.exchangeData.base.getExchangeDataByDelegatorDelegatePair(yield this.dataOwnerApi.getCurrentDataOwnerId(), delegateId);
            const decryptionKeys = this.keyManager.getDecryptionKeys();
            const decryptedInformation = [];
            for (const exchangeData of exchangeDataToDelegate) {
                const decryptedData = yield this.exchangeData.base.tryRawDecryptExchangeData(exchangeData, decryptionKeys);
                if (decryptedData !== undefined) {
                    decryptedInformation.push(Object.assign(Object.assign({}, decryptedData), { exchangeDataId: exchangeData.id }));
                }
            }
            return this.recoveryDataEncryption.createAndSaveExchangeDataRecoveryData(delegateId, decryptedInformation, options.lifetimeSeconds);
        });
    }
    /**
     * Recover the content of exchange data from the delegator that created the recovery data at the provided.
     * {@link recoveryKey} to the current delegate. This will enable the current user to access the exchange data with
     * any of his private keys available on the device from which this method was called.
     * The exchange data will be automatically deleted from the server after the process completes successfully.
     *
     * @param recoveryKey the result of a call to {@link createExchangeDataRecoveryInfo} by a delegator.
     * @return null on success or a failure reason if the recovery data could not be used to perform the operation.
     * @throws If the recovery data is valid but the process fails for other reasons.
     */
    recoverExchangeData(recoveryKey) {
        return __awaiter(this, void 0, void 0, function* () {
            const selfEncryptionKeys = Object.fromEntries(this.keyManager.getSelfVerifiedKeys().map((k) => [k.fingerprint, k.pair.publicKey]));
            const recoveredExchangeData = yield this.recoveryDataEncryption.getAndDecryptExchangeDataRecoveryData(recoveryKey);
            if ('failure' in recoveredExchangeData) {
                return recoveredExchangeData.failure;
            }
            for (const exchangeDataInfo of recoveredExchangeData.success) {
                const retrievedData = yield this.exchangeData.base.getExchangeDataById(exchangeDataInfo.exchangeDataId);
                if (!retrievedData) {
                    console.warn(`Could not recover exchange data with id ${exchangeDataInfo.exchangeDataId} as it was not found. Ignoring`);
                }
                else {
                    yield this.exchangeData.base.updateExchangeDataWithRawDecryptedContent({
                        exchangeData: retrievedData,
                        newEncryptionKeys: selfEncryptionKeys,
                        rawExchangeKey: exchangeDataInfo.rawExchangeKey,
                        rawAccessControlSecret: exchangeDataInfo.rawAccessControlSecret,
                        rawSharedSignatureKey: exchangeDataInfo.rawSharedSignatureKey,
                        newDelegatorSignatureKeys: {},
                    });
                }
            }
            yield this.baseRecoveryApi.deleteRecoveryData(yield this.recoveryDataEncryption.recoveryKeyToId(recoveryKey)).catch((e) => {
                console.warn(`Could not delete recovery data with id ${recoveryKey} after successful recovery: ${e}. Ignoring.`);
            });
            yield this.exchangeData.clearOrRepopulateCache();
            return null;
        });
    }
    /**
     * Deletes the recovery information associated to a certain recovery key. You can use this method with the recovery key for any kind of data,
     * regardless of how you obtained the recovery key (from the {@link createRecoveryInfoForAvailableKeyPairs} or from the
     * {@link createExchangeDataRecoveryInfo} methods).
     * If there is no data associated to the provided recovery key, this method will do nothing.
     * @param recoveryKey the recovery key associated to the recovery information to delete.
     */
    deleteRecoveryInfo(recoveryKey) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.baseRecoveryApi.deleteRecoveryData(yield this.recoveryDataEncryption.recoveryKeyToId(recoveryKey));
        });
    }
    /**
     * Deletes the recovery information associated to a certain data owner, regardless of type.
     * @param dataOwnerId the data owner for which to delete the recovery data.
     * @return the number of deleted recovery information.
     */
    deleteAllRecoveryInfoFor(dataOwnerId) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.getCountFromDeleteAllRes(yield this.baseRecoveryApi.deleteAllRecoveryDataForRecipient(dataOwnerId));
        });
    }
    /**
     * Deletes all key pair recovery information for a certain data owner.
     * @param dataOwnerId the data owner for which to delete the key pair recovery information.
     * @return the number of deleted key pair recovery information.
     */
    deleteAllKeyPairRecoveryInfoFor(dataOwnerId) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.getCountFromDeleteAllRes(yield this.baseRecoveryApi.deleteAllRecoveryDataOfTypeForRecipient(RecoveryData_1.RecoveryData.Type.KEYPAIR_RECOVERY, dataOwnerId));
        });
    }
    /**
     * Deletes all exchange data recovery information for a certain data owner.
     * @param dataOwnerId the data owner for which to delete the exchange data recovery information.
     * @return the number of deleted exchange data recovery information.
     */
    deleteAllExchangeDataRecoveryInfoFor(dataOwnerId) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.getCountFromDeleteAllRes(yield this.baseRecoveryApi.deleteAllRecoveryDataOfTypeForRecipient(RecoveryData_1.RecoveryData.Type.EXCHANGE_KEY_RECOVERY, dataOwnerId));
        });
    }
    getCountFromDeleteAllRes(deleteAllRes) {
        return __awaiter(this, void 0, void 0, function* () {
            if (deleteAllRes.numberValue !== undefined)
                return deleteAllRes.numberValue;
            throw new Error(`Unexpected result from delete method: ${JSON.stringify(deleteAllRes)}`);
        });
    }
}
exports.IccRecoveryXApi = IccRecoveryXApi;
//# sourceMappingURL=icc-recovery-x-api.js.map