"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());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const authentication_component_1 = require("./authentication-component");
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
const iso_libs_1 = require("../libs/iso-libs");
/**
 * token handed over to the user's browser, serves as password to encrypt/decrypt the Medium-access token
 * @type {string}
 */
exports.IC_WEB_TOKEN = "IC_WEB_TOKEN";
/**
 * unique id of the user, comes from the provider (GitHub, Medium, etc)
 * @type {string}
 */
exports.IC_USER_ID = 'IC_USER_ID';
exports.EMAIL_CONFIRMATION_PARAM = "confirmationtoken";
exports.EMAIL_PARAM = "email";
exports.PASSWORD_PARAM = "password";
exports.AUTH_STATUS = {
    PENDING: "pending",
    ACTIVE: "active" // the authentication is active
};
/**
 * This is an Express middleware that checks whether there is a cookie in the header that contains valid login
 * data
 *
 * @param req
 * @param res
 * @param next
 * @returns if successful, it calls the next middleware, if not, it throws an exception that causes the
 * next error handler to be called
 */
exports.createAuthMiddleware = (clientSecret, onAuthenticated) => (req, res, next) => {
    //console.log("createAuthMiddleware", req.universalCookies);
    const webtoken = req.universalCookies.get(exports.IC_WEB_TOKEN);
    const userId = req.universalCookies.get(exports.IC_USER_ID);
    if (webtoken !== undefined && userId !== undefined) {
        //console.log("webtoken: ", webtoken);
        //console.log("userId: ", userId);
        try {
            const decoded = jsonwebtoken_1.default.verify(webtoken, clientSecret);
            if (decoded !== undefined) {
                const { id } = decoded;
                //console.log("id: ", id);
                // we might have numbers... then the "===" comparison does not work
                if (id.toString() === userId.toString()) {
                    // the token contains the correct id
                    //console.log("token matches :-)")
                    onAuthenticated(id.toString());
                    return next();
                }
            }
            return next(authentication_component_1.AUTH_MESSAGE.FAILED);
            //throw new Error("UserId in Token does not match UserId in cookie");
        }
        catch (err) {
            return next(authentication_component_1.AUTH_MESSAGE.FAILED);
            //throw new Error(err);
        }
    }
    else {
        return next(authentication_component_1.AUTH_MESSAGE.NOTLOGGEDIN);
        //throw new Error('No token present!');
    }
};
const getEncryptedAccessToken = (id, clientSecret, access_token) => {
    const today = new Date();
    const expirationDate = new Date(today);
    expirationDate.setDate(today.getDate() + 60);
    // we use the clientSecret to sign the webtoken
    const webtoken = jsonwebtoken_1.default.sign({
        id: id,
        exp: expirationDate.getTime() / 1000,
    }, clientSecret);
    // now let's use the webtoken to encrypt the access token
    const encryptedAccessToken = jsonwebtoken_1.default.sign({
        id: id,
        accessToken: access_token,
        exp: expirationDate.getTime() / 1000,
    }, webtoken);
    return {
        webtoken: webtoken,
        encryptedAccessToken: encryptedAccessToken
    };
};
/**
 * Use this middleware at the endpoint that is specified as the callback-url.
 *
 * @param fetchAccessToken function that can be called to fetch the access Token
 * @param getUserData function to get the userData, takes as input the response from the accessToken-request
 * @param clientSecret
 * @param callbackUrl
 * @param storeAuthData
 * @returns {any}
 */
exports.createCallbackMiddleware = (clientSecret, fetchAccessToken, getUserData, storeAuthData, getAuthData, loginUrl) => function (req, res, next) {
    return __awaiter(this, void 0, void 0, function* () {
        const path = require('path');
        //console.log("THIS IS THE AUTH CALLBACK - authMiddleware");
        // we use this middleware also as endpoint for email confirmation, then the token-parameter must be specified
        const email_confirmation = req.query[exports.EMAIL_CONFIRMATION_PARAM];
        const email_param = req.query[exports.EMAIL_PARAM];
        const password_param = req.query[exports.PASSWORD_PARAM];
        const page = req.query["page"];
        //console.log("received params: ", email_confirmation, email_param, password_param);
        if (email_param) {
            // get the entry of the database
            const authDataList = yield getAuthData(req, // request: any
            false, //matchBrowserIdentity -- we do not want to match the browser identity, the user might use another browser to confirm he mail address
            exports.IC_USER_ID, // key: string
            email_param //val: any,
            );
            //console.log("retrieved auth-data-list: ", authDataList);
            // check whether the user already exists
            const parsedAuthDataList = authDataList.map(raw => JSON.parse(raw.jsonData));
            // the user logs in with her email and password
            if (password_param !== undefined && parsedAuthDataList.length > 0) {
                const authData = parsedAuthDataList
                    .reduce((result, cur) => result !== undefined ? (
                // check whether we have a better state!
                cur.status == exports.AUTH_STATUS.ACTIVE ? cur : result) : (
                // check whether the password is correct
                cur.encrypted_password === password_param ? cur : undefined), undefined);
                if (authData !== undefined) {
                    if (authData.status == exports.AUTH_STATUS.PENDING) {
                        return next(authentication_component_1.AUTH_MESSAGE.VERIFICATIONPENDING);
                    }
                    // create a new webtoken, i.e. other browser will be logged out!
                    const { webtoken, encryptedAccessToken } = getEncryptedAccessToken(email_param, clientSecret, password_param);
                    // put the encrypted web token into the database, this is user (browser)-specific data!
                    const storeResult = yield storeAuthData(req, // request: any
                    exports.IC_USER_ID, // key: string
                    email_param, //val: any,
                    Object.assign({}, authData, {
                        encryptedAccessToken: encryptedAccessToken
                    }));
                    req.universalCookies.set(exports.IC_WEB_TOKEN, webtoken, { path: '/' });
                    req.universalCookies.set(exports.IC_USER_ID, email_param, { path: '/' });
                    //console.log("store password verified result: ", storeResult);
                    res.redirect(`${path.join(iso_libs_1.getBasename(), page !== undefined ? page : loginUrl)}?message=${authentication_component_1.AUTH_MESSAGE.SUCCESS}`);
                }
                else {
                    //console.log ("could not verify password, ", password_param,email_param);
                    return next(authentication_component_1.AUTH_MESSAGE.FAILED);
                }
                return;
            }
            else if (email_confirmation && parsedAuthDataList.length > 0) {
                // the user clicks the link from within the confirmation email
                const authData = parsedAuthDataList
                    .reduce((result, cur) => result !== undefined ? result : (cur.encryptedAccessToken === email_confirmation ? cur : undefined), undefined);
                //console.log("retrieved auth-data: ", authData);
                if (authData !== undefined) {
                    const { webtoken, encryptedAccessToken } = getEncryptedAccessToken(email_param, clientSecret, email_confirmation);
                    // put the encrypted web token into the database, this is user (browser)-specific data!
                    const storeResult = yield storeAuthData(req, // request: any
                    exports.IC_USER_ID, // key: string
                    email_param, //val: any,
                    Object.assign({}, authData, {
                        status: exports.AUTH_STATUS.ACTIVE,
                        encryptedAccessToken: encryptedAccessToken
                    }));
                    //console.log("webtoken: ", webtoken, email_param)
                    req.universalCookies.set(exports.IC_WEB_TOKEN, webtoken, { path: '/' });
                    req.universalCookies.set(exports.IC_USER_ID, email_param, { path: '/' });
                    //console.log("store email verified result: ", storeResult);
                    res.redirect(`${path.join(iso_libs_1.getBasename(), page !== undefined ? page : loginUrl)}?message=${authentication_component_1.AUTH_MESSAGE.MAILVERIFIED}`);
                }
                else {
                    //console.log ("could not verify access token, ", email_confirmation,email_param);
                    return next(authentication_component_1.AUTH_MESSAGE.FAILED);
                }
                return;
            }
        }
        const { redirectPage, fFetch } = fetchAccessToken(req);
        // store the redirectPage in the request for further processing
        console.log("redirect to: ", redirectPage);
        req["redirectPage"] = redirectPage;
        yield fFetch().then(function (resJson) {
            return __awaiter(this, void 0, void 0, function* () {
                //const { token_type, access_token /*, refresh_token, scope, expires_at */} = resJson;
                // try the freshly acquired token and get the user's Medium.com id
                yield getUserData(resJson).then(function (data) {
                    return __awaiter(this, void 0, void 0, function* () {
                        //console.log("get user data: ", JSON.stringify(data));
                        const { id, name, username, imageUrl, access_token, email, status } = data;
                        //console.log("id: ", id);
                        //console.log("name: ", name);
                        const { webtoken, encryptedAccessToken } = getEncryptedAccessToken(id, clientSecret, access_token);
                        //console.log("encryptedAccessToken: ", encryptedAccessToken);
                        // TODO id may be undefined when the token expired!
                        //console.log("storeAuthData: ", storeAuthData)
                        // put the encrypted web token into the database, this is user (browser)-specific data!
                        const storeResult = yield storeAuthData(req, // request: any
                        exports.IC_USER_ID, // key: string
                        id, //val: any,
                        Object.assign({
                            /** We only store the encrypted token when we have an active status, i.e. a auth-provider
                             * we keep it in clear-text for e-mail  */
                            encryptedAccessToken: status === exports.AUTH_STATUS.ACTIVE ? encryptedAccessToken : access_token,
                            name: name,
                            username: username,
                            imageUrl: imageUrl,
                            email: email,
                            status: status,
                        }, password_param ? {
                            encrypted_password: password_param
                        } : {}) //jsonData: any
                        );
                        //console.log("storeResult: ", storeResult);
                        // give the webtoken to back to the user - if the account is valid, only!
                        if (status === exports.AUTH_STATUS.ACTIVE) {
                            req.universalCookies.set(exports.IC_WEB_TOKEN, webtoken, { path: '/' });
                            req.universalCookies.set(exports.IC_USER_ID, id, { path: '/' });
                        }
                        //console.log("done") //'http://' +path.join(req.headers.host +  +
                        res.redirect(path.join(iso_libs_1.getBasename(), redirectPage));
                        return;
                    });
                });
            });
        });
    });
};
//# sourceMappingURL=auth-middleware.js.map