"use strict";
/*
 * server component for the TimeLimit App
 * Copyright (C) 2019 - 2023 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.createParentRouter = void 0;
const body_parser_1 = require("body-parser");
const crypto_1 = require("crypto");
const express_1 = require("express");
const http_errors_1 = require("http-errors");
const config_1 = require("../config");
const account_deletion_1 = require("../function/cleanup/account-deletion");
const remove_device_1 = require("../function/device/remove-device");
const create_add_device_token_1 = require("../function/parent/create-add-device-token");
const create_family_1 = require("../function/parent/create-family");
const get_status_by_mail_address_1 = require("../function/parent/get-status-by-mail-address");
const link_mail_address_1 = require("../function/parent/link-mail-address");
const recover_parent_password_1 = require("../function/parent/recover-parent-password");
const sign_in_into_family_1 = require("../function/parent/sign-in-into-family");
const u2f_1 = require("../function/u2f");
const identity_token_1 = require("../util/identity-token");
const validator_1 = require("./validator");
const createParentRouter = ({ database, websocket, eventHandler }) => {
    const router = (0, express_1.Router)();
    router.post('/get-status-by-mail-address', (0, body_parser_1.json)(), async (req, res, next) => {
        try {
            if (!(0, validator_1.isMailAuthTokenRequestBody)(req.body)) {
                throw new http_errors_1.BadRequest();
            }
            const { mailAuthToken } = req.body;
            const { status, mail } = await database.transaction(async (transaction) => {
                return (0, get_status_by_mail_address_1.getStatusByMailToken)({ database, mailAuthToken, transaction });
            });
            res.json({
                status,
                mail,
                canCreateFamily: !config_1.config.disableSignup,
                alwaysPro: config_1.config.alwaysPro
            });
        }
        catch (ex) {
            next(ex);
        }
    });
    router.post('/create-family', (0, body_parser_1.json)(), async (req, res, next) => {
        try {
            if (config_1.config.disableSignup) {
                throw new http_errors_1.Forbidden();
            }
            if (!(0, validator_1.isCreateFamilyByMailTokenRequest)(req.body)) {
                throw new http_errors_1.BadRequest();
            }
            const result = await (0, create_family_1.createFamily)({
                database,
                eventHandler,
                firstParentDevice: req.body.parentDevice,
                mailAuthToken: req.body.mailAuthToken,
                password: req.body.parentPassword,
                deviceName: req.body.deviceName,
                parentName: req.body.parentName,
                timeZone: req.body.timeZone,
                clientLevel: req.body.clientLevel || null
            });
            res.json({
                deviceAuthToken: result.deviceAuthToken,
                ownDeviceId: result.deviceId,
                data: result.data
            });
        }
        catch (ex) {
            next(ex);
        }
    });
    router.post('/sign-in-into-family', (0, body_parser_1.json)(), async (req, res, next) => {
        try {
            if (!(0, validator_1.isSignIntoFamilyRequest)(req.body)) {
                throw new http_errors_1.BadRequest();
            }
            const result = await (0, sign_in_into_family_1.signInIntoFamily)({
                database,
                eventHandler,
                newDeviceInfo: req.body.parentDevice,
                mailAuthToken: req.body.mailAuthToken,
                deviceName: req.body.deviceName,
                clientLevel: req.body.clientLevel || null,
                websocket
            });
            res.json({
                deviceAuthToken: result.deviceAuthToken,
                ownDeviceId: result.deviceId,
                data: result.data
            });
        }
        catch (ex) {
            next(ex);
        }
    });
    router.post('/recover-parent-password', (0, body_parser_1.json)(), async (req, res, next) => {
        try {
            if (!(0, validator_1.isRecoverParentPasswordRequest)(req.body)) {
                throw new http_errors_1.BadRequest();
            }
            await (0, recover_parent_password_1.recoverParentPassword)({
                database,
                websocket,
                password: req.body.password,
                mailAuthToken: req.body.mailAuthToken
            });
            res.json({ ok: true });
        }
        catch (ex) {
            next(ex);
        }
    });
    async function assertAuthValidAndReturnDetails({ deviceAuthToken, parentId, secondPasswordHash, transaction }) {
        const deviceEntry = await database.device.findOne({
            where: {
                deviceAuthToken: deviceAuthToken
            },
            transaction
        });
        if (!deviceEntry) {
            throw new http_errors_1.Unauthorized();
        }
        if (secondPasswordHash === 'device') {
            if (!deviceEntry.isUserKeptSignedIn) {
                throw new http_errors_1.Unauthorized();
            }
            const parentEntry = await database.user.findOne({
                where: {
                    familyId: deviceEntry.familyId,
                    type: 'parent',
                    userId: deviceEntry.currentUserId
                },
                transaction
            });
            if (!parentEntry) {
                throw new http_errors_1.Unauthorized();
            }
            return { deviceEntry, parentEntry };
        }
        else if (secondPasswordHash.startsWith('u2f:')) {
            try {
                const familyEntryUnsafe = await database.family.findOne({
                    where: {
                        familyId: deviceEntry.familyId
                    },
                    transaction,
                    attributes: ['hasFullVersion']
                });
                if (!familyEntryUnsafe) {
                    throw new http_errors_1.Unauthorized();
                }
                const familyEntry = { hasFullVersion: familyEntryUnsafe.hasFullVersion };
                const hasFullVersion = familyEntry.hasFullVersion || config_1.config.alwaysPro;
                const u2fResult = await (0, u2f_1.validateU2fIntegrity)({
                    integrity: secondPasswordHash,
                    hasFullVersion,
                    familyId: deviceEntry.familyId,
                    deviceId: deviceEntry.deviceId,
                    database,
                    transaction,
                    calculateHmac: (secret) => (0, crypto_1.createHmac)('sha256', secret)
                        .update('direct action')
                        .digest()
                });
                if (u2fResult.userId !== parentId)
                    throw new http_errors_1.Unauthorized();
                const parentEntry = await database.user.findOne({
                    where: {
                        familyId: deviceEntry.familyId,
                        type: 'parent',
                        userId: u2fResult.userId
                    },
                    transaction
                });
                if (!parentEntry) {
                    throw new http_errors_1.Unauthorized();
                }
                return { deviceEntry, parentEntry };
            }
            catch (ex) {
                if (ex instanceof u2f_1.U2fValidationError)
                    throw new http_errors_1.Unauthorized();
                else
                    throw ex;
            }
        }
        else {
            const parentEntry = await database.user.findOne({
                where: {
                    familyId: deviceEntry.familyId,
                    type: 'parent',
                    userId: parentId,
                    secondPasswordHash: secondPasswordHash
                },
                transaction
            });
            if (!parentEntry) {
                throw new http_errors_1.Unauthorized();
            }
            return { deviceEntry, parentEntry };
        }
    }
    router.post('/create-add-device-token', (0, body_parser_1.json)(), async (req, res, next) => {
        try {
            if (!(0, validator_1.isCreateRegisterDeviceTokenRequest)(req.body)) {
                throw new http_errors_1.BadRequest();
            }
            const { token, deviceId } = await database.transaction(async (transaction) => {
                const { deviceEntry } = await assertAuthValidAndReturnDetails({
                    deviceAuthToken: req.body.deviceAuthToken,
                    parentId: req.body.parentId,
                    secondPasswordHash: req.body.parentPasswordSecondHash,
                    transaction
                });
                return (0, create_add_device_token_1.createAddDeviceToken)({ familyId: deviceEntry.familyId, database, transaction });
            });
            res.json({ token, deviceId });
        }
        catch (ex) {
            next(ex);
        }
    });
    router.post('/link-mail-address', (0, body_parser_1.json)(), async (req, res, next) => {
        try {
            if (!(0, validator_1.isLinkParentMailAddressRequest)(req.body)) {
                throw new http_errors_1.BadRequest();
            }
            await (0, link_mail_address_1.linkMailAddress)({
                mailAuthToken: req.body.mailAuthToken,
                deviceAuthToken: req.body.deviceAuthToken,
                parentPasswordSecondHash: req.body.parentPasswordSecondHash,
                parentUserId: req.body.parentUserId,
                websocket,
                database
            });
            res.json({ ok: true });
        }
        catch (ex) {
            next(ex);
        }
    });
    router.post('/remove-device', (0, body_parser_1.json)(), async (req, res, next) => {
        try {
            if (!(0, validator_1.isRemoveDeviceRequest)(req.body)) {
                throw new http_errors_1.BadRequest();
            }
            await database.transaction(async (transaction) => {
                const { deviceEntry } = await assertAuthValidAndReturnDetails({
                    deviceAuthToken: req.body.deviceAuthToken,
                    parentId: req.body.parentUserId,
                    secondPasswordHash: req.body.parentPasswordSecondHash,
                    transaction
                });
                await (0, remove_device_1.removeDevice)({
                    database,
                    familyId: deviceEntry.familyId,
                    deviceId: req.body.deviceId,
                    websocket,
                    transaction
                });
            });
            res.json({ ok: true });
        }
        catch (ex) {
            next(ex);
        }
    });
    router.post('/create-identity-token', (0, body_parser_1.json)(), async (req, res, next) => {
        try {
            if (!(0, validator_1.isRequestIdentityTokenRequest)(req.body)) {
                throw new http_errors_1.BadRequest();
            }
            const body = req.body;
            await database.transaction(async (transaction) => {
                const { deviceEntry, parentEntry } = await assertAuthValidAndReturnDetails({
                    deviceAuthToken: body.deviceAuthToken,
                    parentId: body.parentUserId,
                    secondPasswordHash: body.parentPasswordSecondHash,
                    transaction
                });
                const token = await (0, identity_token_1.createIdentityToken)({
                    purpose: body.purpose,
                    familyId: deviceEntry.familyId,
                    userId: parentEntry.userId,
                    mail: parentEntry.mail
                });
                res.json({ token });
            });
        }
        catch (ex) {
            if (ex instanceof identity_token_1.MissingSignSecretException)
                res.sendStatus(404);
            else
                next(ex);
        }
    });
    router.post('/delete-account', (0, body_parser_1.json)(), async (req, res, next) => {
        try {
            if (!(0, validator_1.isDeleteAccountPayload)(req.body)) {
                throw new http_errors_1.BadRequest();
            }
            await (0, account_deletion_1.deleteAccount)({ database, request: req.body, websocket });
            res.sendStatus(200);
        }
        catch (ex) {
            next(ex);
        }
    });
    return router;
};
exports.createParentRouter = createParentRouter;
//# sourceMappingURL=parent.js.map