/*
 * Copyright (c) 2022.
 * Author Peter Placzek (tada5hi)
 * For the full copyright and license information,
 * view the LICENSE file that was distributed with this source code.
 */

import {AbilityManager, AuthorizationHeaderValue} from "@typescript-auth/core";
import {verifyToken} from "@typescript-auth/server";
import {UnauthorizedError} from "@typescript-error/http";
import {getCustomRepository} from "typeorm";
import {UserRepository} from "@chamesu/common/domains/auth/user/repository";
import {getWritableDirPath} from "../../paths";
import {TokenPayload} from "@chamesu/common/domains/auth/token/type";

const ip4ToInt = (ip: string) =>
    ip.split('.').reduce((int, oct) => (int << 8) + parseInt(oct, 10), 0) >>> 0;

const isIp4InCidr = (ip: string, cidr: string) => {
    const [range, bits = 32] = cidr.split('/');
    const mask = ~(2 ** (32 - Number(bits)) - 1);
    return (ip4ToInt(ip) & mask) === (ip4ToInt(range) & mask);
};

export async function authenticateWithAuthorizationHeader(request: any, value: AuthorizationHeaderValue) : Promise<void> {
    let userId : number | string | undefined;

    switch (value.type) {
        case "Bearer":
            const tokenPayload: TokenPayload = await verifyToken(value.token, {
                directory: getWritableDirPath()
            });

            const {sub} = tokenPayload;
            userId = sub;

            // remove ipv6 space from embedded ipv4 address
            const tokenAddress = tokenPayload.remoteAddress.replace('::ffff:', '');
            const currentAddress : string = request.ip.replace('::ffff:', '');

            if(typeof tokenPayload.remoteAddress !== 'string') {
                return;
            }

            if(
                // ipv4 + ipv6 local addresses
                ['::1', '127.0.0.1'].indexOf(currentAddress) === -1 &&
                tokenAddress !== currentAddress &&
                // allow private network addresses, maybe explicit whitelist possible frontend ip addresses instead.
                !isIp4InCidr(currentAddress, '10.0.0.0/8') &&
                !isIp4InCidr(currentAddress, '172.16.0.0/12') &&
                !isIp4InCidr(currentAddress, '192.168.0.0/16')
            ) {
                throw new UnauthorizedError();
            }
            break;
        case "Basic":
            const userRepository = getCustomRepository(UserRepository);

            const verified = await userRepository.verifyCredentials(value.username, value.password);
            if(typeof verified !== 'undefined') {
                userId = verified.id;
            }
            break;
    }

    if(typeof userId === 'undefined') {
        return;
    }

    const userRepository = getCustomRepository<UserRepository>(UserRepository);
    const userQuery = userRepository.createQueryBuilder('user')
        .addSelect('user.email')
        .where('user.id = :id', {id: userId});

    const user = await userQuery.getOne();

    if (typeof user === 'undefined') {
        throw new UnauthorizedError();
    }

    const permissions =  await userRepository.getOwnedPermissions(user.id);

    request.user = user;
    request.userId = user.id;

    request.ability = new AbilityManager(permissions);
}

export function parseCookie(request: any) : string | undefined {
    try {
        if (typeof request.cookies?.auth_token !== 'undefined') {
            const {access_token} = JSON.parse(request.cookies?.auth_token);

            return access_token;
        }
    } catch (e) {
        // don't handle error, this is just fine :)
    }

    return undefined;
}
