"use strict";
/*
 * server component for the TimeLimit App
 * Copyright (C) 2019 - 2022 Jonas Lochmann
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.assertActionIntegrity = assertActionIntegrity;
const crypto_1 = require("crypto");
const binary_number_1 = require("../../../util/binary-number");
const u2f_1 = require("../../u2f");
const integrity_1 = require("./exception/integrity");
const illegal_state_1 = require("./exception/illegal-state");
async function assertActionIntegrity({ action, cache, deviceId }) {
    if (action.type === 'parent') {
        if (action.integrity === 'device') {
            const deviceEntryUnsafe = await cache.database.device.findOne({
                attributes: ['currentUserId'],
                where: {
                    familyId: cache.familyId,
                    deviceId,
                    currentUserId: action.userId,
                    isUserKeptSignedIn: true
                },
                transaction: cache.transaction
            });
            if (!deviceEntryUnsafe) {
                throw new integrity_1.ParentDeviceActionWithoutParentDeviceException();
            }
            // this ensures that the parent exists
            await cache.getSecondPasswordHashOfParent(action.userId);
            return {
                isChildLimitAdding: false,
                authentication: 'device'
            };
        }
        else if (action.integrity === 'childDevice') {
            return {
                isChildLimitAdding: true, // will be checked later
                authentication: 'device'
            };
        }
        else if (action.integrity.startsWith('u2f:')) {
            // this ensures that the parent exists
            await cache.getSecondPasswordHashOfParent(action.userId);
            try {
                const checkResult = await (0, u2f_1.validateU2fIntegrity)({
                    integrity: action.integrity,
                    hasFullVersion: cache.hasFullVersion,
                    familyId: cache.familyId,
                    deviceId,
                    database: cache.database,
                    transaction: cache.transaction,
                    calculateHmac: (secret) => calculateActionHmac({
                        action,
                        deviceId,
                        secret
                    })
                });
                if (checkResult.userId !== action.userId) {
                    throw new integrity_1.InvalidParentActionIntegrityValue();
                }
            }
            catch (ex) {
                if (ex instanceof u2f_1.U2fValidationError)
                    throw new integrity_1.InvalidU2fIntegrityValue(ex.message);
                else
                    throw ex;
            }
            return {
                isChildLimitAdding: false,
                authentication: 'u2f'
            };
        }
        else if (action.integrity.startsWith('password:')) {
            // password method with hmac
            const parentSecondHash = await cache.getSecondPasswordHashOfParent(action.userId);
            const correctResponse = calculateActionHmac({
                action,
                deviceId,
                secret: Buffer.from(parentSecondHash, 'utf8')
            });
            const providedResult = Buffer.from(action.integrity.substring(9), 'base64');
            if (!(0, crypto_1.timingSafeEqual)(providedResult, correctResponse)) {
                throw new integrity_1.InvalidParentActionIntegrityValue();
            }
            return {
                isChildLimitAdding: false,
                authentication: 'password'
            };
        }
        else {
            // legacy password method
            const parentSecondHash = await cache.getSecondPasswordHashOfParent(action.userId);
            const integrityData = action.sequenceNumber.toString(10) +
                deviceId +
                parentSecondHash +
                action.encodedAction;
            const expectedIntegrityValue = (0, crypto_1.createHash)('sha512').update(integrityData).digest('hex');
            if (action.integrity !== expectedIntegrityValue) {
                throw new integrity_1.InvalidParentActionIntegrityValue();
            }
            return {
                isChildLimitAdding: false,
                authentication: 'password'
            };
        }
    }
    else if (action.type === 'child') {
        const childSecondHash = await cache.getSecondPasswordHashOfChild(action.userId);
        const integrityData = action.sequenceNumber.toString(10) +
            deviceId +
            childSecondHash +
            action.encodedAction;
        const expectedIntegrityValue = (0, crypto_1.createHash)('sha512').update(integrityData).digest('hex');
        if (action.integrity !== expectedIntegrityValue) {
            throw new integrity_1.InvalidChildActionIntegrityValue();
        }
        return {
            isChildLimitAdding: false,
            authentication: 'password'
        };
    }
    else {
        throw new illegal_state_1.ActionObjectTypeNotHandledException();
    }
}
function calculateActionHmac({ action, deviceId, secret }) {
    const binaryDeviceId = Buffer.from(deviceId, 'utf8');
    const binaryAction = Buffer.from(action.encodedAction, 'utf8');
    return (0, crypto_1.createHmac)('sha256', secret)
        .update((0, binary_number_1.longToBuffer)(BigInt(action.sequenceNumber)))
        .update((0, binary_number_1.intToBuffer)(binaryDeviceId.length))
        .update(binaryDeviceId)
        .update((0, binary_number_1.intToBuffer)(binaryAction.length))
        .update(binaryAction)
        .digest();
}
//# sourceMappingURL=integrity.js.map