<% /*
    $Revision$
    $Id$
    File:               shared/login.inc
    Description:        Inlog functionaliteit
    Parameters:
    Context:
    Note:               LET OP: Dit bestand heeft een heel klein stukje VBScript in zich
                        Dat maakt dat je dit bestand *niet* meer overal (via common.inc
                        of zo) moet gaan includen
    Note2:              json2.js is nodig omdat user.options wordt gebruikt
                        Op zich moet je het dan maar aan diverse asp's toevoegen maar dan
                        moet je ook vele sso.asp's langs. Dan toch maar hier

*/
%>
<!-- #include file="../Shared/json2.js" -->
<%
// Elders is prs_key geauthenticeerd. Registreer die hier als de actieve gebruiker.
function doLogin(prs_key, params)
{
    params = params || {};
    // Paranoia mode
    var sql = "SELECT prs_perslid_login"
            + "  FROM prs_perslid"
            + " WHERE prs_perslid_verwijder IS NULL"
            + "   AND prs_perslid_key = " + prs_key;
    var oRs = Oracle.Execute(sql);
    if (oRs.Eof)
    {
        __DoLog("Niet bestaande of verwijderde persoon " + prs_key + " geweigerd in doLogin.")
        eval("INTERNAL_ERROR_INVALID_LOGIN_" + prs_key);
    }
    var first_login = (oRs("prs_perslid_login").Value == null);
    oRs.Close();

    if ("isFACFACinternal" in params) // vanuit JWT-sso
    {
        var deze = new Perslid(prs_key);
        // SSO naar een FACFAC gebruiker mag alleen als fac_idp_internal aan staat
        if (deze.has("WEB_FACFAC") && !params.isFACFACinternal)
        {
            __DoLog("Illegal login WEB_FACFAC");
            shared.internal_error("This IDP cannot be used for users with WEB_FACFAC (prs_key={0}).".format(prs_key));
        }
        // Als fac_idp_internal aan staat mag alleen je alleen SSO doen naar een FACFAC gebruiker
        // Tenzij S("idp_internal_anyuser") true is, dan mag je toch naar iedereen
        // Dat doen we op OTA via custenc.wsc, dat doen we niet in PROD
        if (params.isFACFACinternal && !S("idp_internal_anyuser") && !deze.has("WEB_FACFAC"))
        {
            shared.internal_error("This IDP can only be used for users with WEB_FACFAC (prs_key={0}).".format(prs_key));
        }
    }

    // Alvast nieuwe user_key loggen zodat je ziet wie er inlogt.
    if (typeof NO_ADDHEADER == "undefined" && user_key != prs_key && Request.Servervariables("HTTP_FCLT_VERSION").Count > 0)
    {   // wordt opgepikt door FCLTAPI.DLL voor in de logging en daarna gestript. Niet in Fiddler te zien dus
        Response.AddHeader ("FCLT_USERID", customerId + "\\" + String(prs_key));
    }

    /* global */ user_key = prs_key;
    //user_lang = oRs(1).Value; // globale moet er nog uit!
    if (typeof LCL_Disable == "undefined")
        lcl.loadLCL();

    setASPFIXATION();

    Session.Contents.Remove("has_no_remote_res"); // Dat weten we nu nog niet zeker
    var ip = String(Request.ServerVariables("REMOTE_ADDR"));
    Session("last_ip") = ip; // onthouden voor ip_locking

    if (!params.noFacSession) // AAEN infobord krijgt er anders 10 per minuut
    {
        // Nu maar eens oudere opruimen. Effectief blijft altijd
        // de laatste sessie van een gebruiker staan.
        // Mogelijk ruim je ook toch verlopen sessies van anderen op. Geen probleem.
        var sql = "DELETE FROM fac_session"
               + "  WHERE fac_session_expire < SYSDATE";
        Oracle.Execute(sql);

        var agent = String(Request.ServerVariables("HTTP_USER_AGENT"));

        // Welbeschouwd gebruiken we het volgende FAC_SESSION record nooit, we werken
        // altijd met ASPSESSION==>IIS SESSION==>user_key
        var sql = "INSERT INTO fac_session"
               + "    (fac_session_sessionid_hash,"
               + "     prs_perslid_key,"
               + "     fac_session_expire,"
               + "     fac_session_useragent,"
               + "     fac_session_ip)"
               + " VALUES(fac.makehash(" + safe.quoted_sql(Session("FACSESSIONID")) + "), "
               +        user_key + ","
               + "      SYSDATE+1, " // 24 uur is genoeg
               +        safe.quoted_sql(agent, 256) + ","
               +        safe.quoted_sql(ip, 64) + ")";
        Oracle.Execute(sql);
    }

    var registersql = "UPDATE prs_perslid"
                    + "   SET prs_perslid_login = SYSDATE"
                    + " WHERE prs_perslid_key=" + user_key;
    Oracle.Execute(registersql);

    Session("user_key") = user_key; // Nu ben je pas *echt* ingelogd
    /* global */ user = new Perslid(user_key);

    Session.Contents.Remove("must_accept_terms");
    if (S("fac_accept_terms") > 0
        && (!(Session("org_user_key") > 0))
        && !user.isGroupedUser()
        && !params.noFacSession
        && !user.has("WEB_FACTAB") // die er niet mee lastig vallen
       )
    {
        var termsPath = custpath + "/bdradrfiles/" + L("lcl_terms_filename");
        var fso = Server.CreateObject("Scripting.FileSystemObject");
        var termsFile = Server.MapPath(termsPath);
        if (!fso.FileExists(termsFile))
        {
            __DoLog("fac_accept_terms is set but '{0}' is not found.".format(termsPath), '#f00');
        }
        else
        {
            var uvers = String(user.terms_version()).split("|")[0]; // Haal de digest er af
            if (uvers != L("lcl_terms_filename"))
                Session("must_accept_terms") = 1; // Wordt opgepikt in common.inc
        }
    }

    // Normaal gesproken zou ik hier new Date() gebruiken
    // Omdat zelfs kleine afwijkingen al grote problemen zouden kunnen
    // geven consequent altijd de Oracle-tijd gebruiken
    var sql = "SELECT SYSDATE FROM DUAL";
    var oRs = Oracle.Execute(sql);
    Session("login_date") = new Date(oRs(0).Value).getTime();
    oRs.Close();

    // FACFAC tracken we altijd
    if (!params.noFacSession) // fac_scan_cust genereert er anders te veel
    {
        if (user.has("WEB_FACFAC"))
            shared.trackaction("PRSLOG", user_key, L("lcl_logged_on").format(Session("ASPFIXATION").slice(-6)));
    }

    if (first_login && S("fac_firstlogin_expire") > 0)
    {
        var sql = "INSERT INTO fac_menu"
                + " ( fac_menu_altlabel"
                + " , fac_menu_alturl"
                + " , fac_menu_altgroep"
                + " , prs_perslid_key"
                + " , fac_menu_volgnr"
                + " ) VALUES"
                + " (" + safe.quoted_sql(L("lcl_firstlogin_url"))
                + " ," + safe.quoted_sql(S("fac_firstlogin_url"))
                + " ,5"
                + " ," + user_key
                + " ,(SELECT 10 + COALESCE(MAX(fac_menu_volgnr), 0)"
                  + "   FROM fac_menu"
                  + "  WHERE prs_perslid_key = " + user_key
                  + ")"
                + " )";
        Oracle.Execute(sql, true);
    }
    else
    {
        var sql = "DELETE FROM fac_menu"
                + " WHERE fac_menu_altgroep = 5"
                + "   AND fac_menu_alturl = " + safe.quoted_sql(S("fac_firstlogin_url"))
                + "   AND prs_perslid_key = " + user_key
                + "   AND fac_menu_aanmaak < SYSDATE - " + S("fac_firstlogin_expire");
        Oracle.Execute(sql, true);
        __Log("Welcome.asp expired and removed");
    }

    var fac_lang = getQParamSafe("fac_lang", "").toUpperCase(); // overrule via param
    // Liever geen session maar m_connections heeft dit al nodig voor zijn fac.initsession
    // Bovendien voorkomen we zo dat een simpele user.lang() al een _require_prs_perslid triggert
    Session("user_lang") = fac_lang || user.dblang();
    return true;
}

// Session.Abondon is gevaarlijk: dan verlies je ook CustomerID etc.
// Bovendien krijg je met IIS dan nog steeds geen nieuwe ASPSESSIONID
function doLogoff()
{
    Session("no_sso") = 1; // Voorkom autosso
    Session.Contents.Remove("user_key");
    Session.Contents.Remove("ASPFIXATION");
    Session.Contents.Remove("must_reset_password");
    Session.Contents.Remove("login_by_fallback");
    deleteSessionCookie("fcltid");
    deleteCookie("ASPFIXATION");
}

// Inloggen via een fcltid-cookie of een session die met QR-code is gescand
function setUserFromSession (p_session)
{
    // TODO: Hier wil ik ooit de user_key eigenlijk ook hebben uit
    //       een cookie zodat deze query efficienter wordt.
    var sql = "SELECT prs_perslid_key, fac_session_data "
            + "  FROM fac_session "
            + " WHERE fac_session_expire > sysdate "
            + "   AND fac_session_sessionid_hash = fac.makehash(" + safe.quoted_sql(p_session) + ")";
    var oRs = Oracle.Execute( sql );
    if (!oRs.eof)
    {
       doLogin(oRs("prs_perslid_key").Value);
       var sessionData = oRs("fac_session_data").value;

       // verwijder de huidige sessie
       sql = "DELETE fac_session"
           + " WHERE prs_perslid_key = " + user_key // index-performance
           + "   AND fac_session_sessionid_hash = fac.makehash(" + safe.quoted_sql(p_session) + ")";
       Oracle.Execute(sql);
       // makeSessionCookie(sessionData); aanroeper bepaalt maar of er een nieuwe sessie komt
    }
    oRs.Close();
}

function makeSessionCookie (sessionData)
{
   var sessionId = shared.random(32);

   // maak nieuwe sessie aan
   var agent = String(Request.ServerVariables("HTTP_USER_AGENT"));
   var ip = String(Request.ServerVariables("REMOTE_ADDR"));

   sql = "INSERT INTO fac_session ( "
       + "   fac_session_sessionid_hash, "
       + "   prs_perslid_key, "
       + "   fac_session_data, "
       + "   fac_session_expire,"
       + "   fac_session_useragent,"
       + "   fac_session_ip) "
       + " VALUES ( "
       +     "fac.makehash(" + safe.quoted_sql(sessionId) + "), "
       +     user_key + ", "
       +     safe.quoted_sql(sessionData) + ", "
       + "   sysdate + " + S("login_remember_days") + ", " // sessie timeout op een half jaar.
       +     safe.quoted_sql(agent, 256) + ","
       +     safe.quoted_sql(ip, 64) + " )"
    Oracle.Execute(sql);

    // set de nieuwe sessionID als cookie.
    Response.Cookies("fcltid")=sessionId;
    Response.Cookies("fcltid").Path = rooturl + "/";
    if (S("auto_https") & 2)
        Response.Cookies("fcltid").Secure=true;
    VBexpireCookie("fcltid", "d", S("login_remember_days"));

    // fcltcust is niet per se nodig voor Facilitor maar wel handig bij interne ontwikkeling
    Response.Cookies("fcltcust") = customerId;
    Response.Cookies("fcltcust").Path = rooturl + "/";
    if (S("auto_https") & 2)
        Response.Cookies("fcltcust").Secure=true;
    VBexpireCookie("fcltcust", "d", S("login_remember_days"));
}

function deleteSessionCookie (cookiename)
{
    var session = String(Request.Cookies(cookiename));
    if (session)
    {
        var sql = "DELETE fac_session"
                + " WHERE prs_perslid_key = " + user_key // index-performance
                + "   AND fac_session_sessionid_hash = fac.makehash(" + safe.quoted_sql(session) + ")";
        Oracle.Execute(sql);
    }

    // Cookie wissen
    deleteCookie(cookiename);
}

function deleteCookie (cookiename)
{
    Response.Cookies(cookiename)="";
    Response.Cookies(cookiename).Path = rooturl + "/";
    if (S("auto_https") & 2)
        Response.Cookies(cookiename).Secure=true;
    VBexpireCookie(cookiename, "yyyy", -1);
}

function pbkdf2(wachtwoord, passsalt, workfactor, len)
{
    //__Log("P: {0}, S: {1}, W: {2}, L:{3}".format(wachtwoord, passsalt, workfactor, len))
    if (workfactor < 1 ||
        workfactor > 20) // workfactor 2^24 (16777216) duurt op FACWS001 zo'n 45 seconde
                         // Daarom voorlopig limiteren op 2^20 (1048576, zo'n 3 seconde)
    {
        __DoLog(workfactor);
        INTERNAL_ERROR_BADWORKFACTOR;
    }
    len = len || S("prs_password_hash_length"); // default 20
    // __Log("Wachtwoord: {0}, salt: {1}, workfactor: {2}, len: {3}".format(wachtwoord, passsalt, Math.pow(2, workfactor), len));
    var oSLNKDWF = new ActiveXObject("SLNKDWF.About");
    var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
    var usStart = oSLNKDWF.usTimer;
    var is_hash = oCrypto.hex_pbkdf2(wachtwoord, passsalt, Math.pow(2, workfactor), len);
    var tm = ((oSLNKDWF.usTimer - usStart)/1000).toFixed(1);
    __Log("Calculating hash with workfactor {0} ({1}) took {2} ms".format(workfactor, Math.pow(2, workfactor), tm));
    return is_hash;
}

function otpcodes(otpsecret)
{
    if (!otpsecret)
        return false;
    var arr = otpsecret.split("$");
    switch (arr[0])
    {
        case "1": // TOTP,  formaat 1$30$size$offset$seed
            if (arr.length != 5)
            {
                __Log(arr);
                INTERNAL_ERROR_BADTOTP;
            }
            var result = { tokentime: new Date(),
                           otpstep: parseInt(arr[1], 10),
                           otpsize: parseInt(arr[2], 10),
                           otpoffset: parseInt(arr[3], 10),
                           otpseed: arr[4],
                           codes: [] };
            //Response.Write("<p>Current time: {0}".format(toDateTimeString(result.tokentime, true)));
            if (result.otpoffset != 0)
            {
                result.tokentime.setSeconds(result.tokentime.getSeconds() + result.otpoffset)
                //Response.Write("</br>Token time: {0} ({1} s)".format(toDateTimeString(result.tokentime, true), result.otpoffset));
            }
            var ts = Math.round(result.tokentime.getTime() / 1000);
            var cnt = Math.floor(ts  / result.otpstep) ; // Google doet 30 seconde
            result.tokenpassed = (ts  / result.otpstep - cnt); // zo ver zijn we al in de periode gevorderd
            __Log("OTP time {0} counter {1}".format(toDateTimeString(result.tokentime, true), cnt));
            var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
            for (var i = -Math.floor(S("prs_password_otp_window") / result.otpstep); i <= Math.floor(S("prs_password_otp_window") / result.otpstep); i++)
            {
                var onecode = { otpshould : oCrypto.hotp(result.otpseed, cnt + i, result.otpsize),
                                dtsfrom: new Date(((cnt + i) * result.otpstep - result.otpoffset) * 1000),
                                dtsto: new Date(((cnt + i + 1) * result.otpstep - result.otpoffset)* 1000),
                                offset: i * result.otpstep,
                                counter: cnt + i
                              }
                result.codes.push(onecode);
            }

            break;
        default:
            INTERNAL_ERROR_BADOTP;
    }
    return result;
}

function testpassword(prs_key, wachtwoord, pmobile)
{
    if (!wachtwoord)
        return false;

    var sql = " SELECT prs_perslid_key"
            + "      , prs_perslid_flags"
            + "      , prs_perslid_authenticatie"
            + "      , prs_perslid_authenticatie_exp"
            + "      , prs_perslid_salt"
            + "      , prs_perslid_wachtwoord_hash"
            + "      , prs_perslid_oslogin"
            + "      , prs_perslid_apikey"
            + "   FROM prs_perslid"
            + "  WHERE prs_perslid_key = " + prs_key;
    var oRs = Oracle.Execute(sql);

    var passsalt = oRs("prs_perslid_salt").Value;
    var passhash = oRs("prs_perslid_wachtwoord_hash").Value;
    var mobauth = oRs("prs_perslid_authenticatie").Value;
    var mobauthexp = new Date(oRs("prs_perslid_authenticatie_exp").Value);
    var apikey = oRs("prs_perslid_apikey").Value;
    var oslogin = oRs("prs_perslid_oslogin").Value;
    oRs.Close();

    if (pmobile==1 && mobauth) // Mobile 'verzonnen' wachtwoord
    {
        var mobww = wachtwoord.toLowerCase(); // wij sturen lowercase base32
        mobww = mobww.replace(/0/i, 'o'); // 0 / 1 / 8 komen daar niet in voor
        mobww = mobww.replace(/1/i, 'l');
        mobww = mobww.replace(/8/i, 'b');
        if (mobauth == mobww && mobauthexp && new Date() <= mobauthexp)
        {
            return true; // Goed
        }
        __Log("Mobile token check failed");
        // Wel doorgaan met gewoon wachtwoord controle, dat staan we ook toe
    }

    // geen wachtwoord
    if (!passhash)
        return false;

    // APItoken wachtwoord  1452611295:0pBCO67Br4Cs7kkm+zszW0JhjlM
    // Voor AAFM#34930 app-authenticatie
    // Aangemaakt in model_persons.inc/fnApiToken
    if (apikey && wachtwoord.match(/[0-9]{10}\:.*/))
    {
        if (protectHMAC.verify(oslogin, wachtwoord, { sleutel: apikey, expire: 60*24*S("fac_apitoken_auth_expire"), relaxed: true }))
            return true;
    }

    // gewoon wachtwoord
    // Noot: we zouden hier kunnen testen op
    //   AND (prs_perslid_wachtwoord_exp IS NULL OR prs_perslid_wachtwoord_exp > SYSDATE)
    // Met een verlopen wachtwoord mag je echter wel inloggen maar wordt je (via common.inc)
    // wel gedwongen daarna je wachtwoord te wijzigen
    var arr = passhash.split("$");
    if (arr.length == 1) // Old style
    {
        var sql = "SELECT 1"
                + "  FROM prs_perslid"
                + " WHERE prs.testpassword(prs_perslid_key, " + safe.quoted_sql(wachtwoord) + ") = 1"
                + "   AND prs_perslid_key = " + prs_key
        var oRs = Oracle.Execute(sql);
        var found = !oRs.Eof;
        oRs.Close();
        if (!found)
            return false;
        // else TODO upgraden!
        var workfactor = 0;
    }
    else // new style
    {
        switch (arr[0])
        {
            case "1": // PBKDF2,  1$workfactor$hash
                var workfactor = parseInt(arr[1], 10);
                var should_hash = arr[2];
                if (arr.length != 3 ||
                    isNaN(workfactor) ||
                    should_hash.length < 40) // workfactor 2^24 (16777216) duurt op FACWS001 zo'n 45 seconde
                                             // Daarom voorlopig limiteren op 2^20 (1048576, zo'n 3 seconde)
                {
                    __DoLog(arr);
                    INTERNAL_ERROR_BADHASH;
                }
                var is_hash = pbkdf2(wachtwoord, passsalt, workfactor, should_hash.length / 2);
                if (should_hash != is_hash)
                {
                    __Log("Hash mismatch {0}<>{1}".format(should_hash, is_hash));
                    return false;
                }
                break;
            default:
                __DoLog(arr);
                INTERNAL_ERROR_UNKNOWN_PASSHASH;
        }
    }
    if (workfactor != S("prs_password_hash_factor"))
        setpassword(prs_key, wachtwoord);
    return true;
}


function setpassword(prs_key, wachtwoord, expired)
{
    if (S("prs_password_hash_factor") == 0 || !wachtwoord) // Old style
    {
        var sql = "BEGIN prs.setpassword(" + prs_key + ", " + safe.quoted_sql(wachtwoord) + "); END;";
        Oracle.Execute(sql);
    }
    else
    {
        var passsalt = shared.random(32);
        var workfactor = S("prs_password_hash_factor");
        var is_hash = pbkdf2(wachtwoord, passsalt, workfactor);
        var sql = "UPDATE prs_perslid"  // Ooit expire op SYSDATE + fac.getsetting ('prs_password_expiration') als die is gevuld
                + "   SET prs_perslid_wachtwoord_exp = " + (expired?"SYSDATE":"NULL")
                + "     , prs_perslid_salt = " + safe.quoted_sql(passsalt)
                + "     , prs_perslid_wachtwoord_hash = " + safe.quoted_sql('1${0}${1}'.format(workfactor, is_hash))
                + " WHERE prs_perslid_key = " + prs_key;
        Oracle.Execute(sql);
    }
}

function testotp (prs_key, otprequest)
{
    var sql = " SELECT prs_perslid_otpsecret"
            + "      , prs_perslid_otpcounter"
            + "   FROM prs_perslid"
            + "  WHERE prs_perslid_key = " + prs_key;

    var oRs = Oracle.Execute(sql);

    var otpsecret = oRs("prs_perslid_otpsecret").Value;
    var otpcounter = oRs("prs_perslid_otpcounter").Value || -1;
    oRs.Close();

    return verify_otp(prs_key, otprequest, otpsecret, otpcounter);
}

function verify_otp (prs_key, otprequest, otpsecret, otpcounter)
{
    var otpresult = otpcodes(otpsecret);

    __Log("Otprequest: " + otprequest);
    var otp_oke = false;
    if (otprequest.length == otpresult.otpsize && otprequest.match(/^[0-9]*$/)) // quick check exact 6 cijfers
    {
        for (var i = 0; i < otpresult.codes.length && !otp_oke; i++)
        {
            var code = otpresult.codes[i];
            __Log(code);
            if (code.counter > otpcounter) // Hij mag niet eerder toegepast zijn
            {
                var otpshould = code.otpshould;
                if (otprequest == code.otpshould)
                {
                    otp_oke = true;
                    otpcounter = code.counter; // TODO: We zouden moeten bijwerken
                    var sql = "UPDATE prs_perslid"
                            + "   SET prs_perslid_otpcounter = " + otpcounter
                            + " WHERE prs_perslid_key = " + prs_key;
                    Oracle.Execute(sql);
                }
            }
        }
    }
    if (!otp_oke)
    {
        __Log("OTP check failed");
        __Log(otpresult);
        return false;
    }
    return otp_oke;
}

//
// zet Session("user_key") als username en wachtwoord geldig zijn.
// Login na verzending via sms moet binnen 1 kwartier ingevuld zijn.
// resultaat: true bij succesvolle login, false bij niet succesvol
// drie username opties:
//     - prs_perslid_oslogin
//     - prs_perslid_oslogin2
//     - upper(prs_perslid_email)
// drie wachtwoord opties
//     - leeg (single-signon)
//     - prs_perslid_wachtwoord (of eigenlijk: prs_perslid_salt en prs_perslid_wachtwoord_hash)
//     - prs_perslid_authenticatie (en prs_perslid_authenticatie_exp > sysdate)
// de laatste wordt gebruikt voor mobile/SMS

/* global */ login_fail_reason = L("lcl_login_wrong");
/* global */ otp_user_key = -1;

function tryLogin(username, wachtwoord, params)
{
    params = params || {};
    Session.Contents.Remove("org_user_key");

    if (!username || username == 'undefined')
        return false;

    if (username.indexOf("\\") > -1)
        username = username.split("\\")[1]; // strip domain name

    var logins = [];
    if (S("login_use_email"))
    {
        logins.push(" upper(prs_perslid_email) = " + safe.quoted_sql_upper(username));
    }
    else if (getQParam("API", ""))
    {
        logins.push(" prs_perslid_apikey = " + safe.quoted_sql_upper(username, 128));
        wachtwoord = null;
    }
    else
    {
        logins.push(" prs_perslid_oslogin = " + safe.quoted_sql_upper(username, 30));
        logins.push(" prs_perslid_oslogin2 = " + safe.quoted_sql_upper(username, 30));
    }
    var sql = " SELECT prs_perslid_key, "
            + "        prs_perslid_flags"
            + "      , prs_perslid_otpsecret"
            + "      , prs_perslid_otpcounter"
            + "   FROM prs_perslid"
            + "  WHERE prs_perslid_verwijder IS NULL"
            + "    AND (" + logins.join(" OR ") + ")"
            + "    AND BITAND(prs_perslid_flags, 1+4+8) = 0"; // 2==unconfirmed staan we nog heel even toe

    var oRs = Oracle.Execute(sql);
    if (oRs.Eof)
        return false; // Gebruikersnaam niet eens gevonden

    var otpsecret = oRs("prs_perslid_otpsecret").Value;
    var otpcounter = oRs("prs_perslid_otpcounter").Value || -1;

    var found = false;
    if (/* nog niet vanuit SAML/default.asp params.noPassword && */ wachtwoord == null) // SSO
        found = true; // En zijn we verder wel klaar
    else
        found = testpassword(oRs("prs_perslid_key").Value, wachtwoord, params.mobile);

    if (!found)
        return false;

    if ((oRs("prs_perslid_flags").Value & 2) == 2)
    {
        login_fail_reason = L("lcl_self_register_unconfirmed");
        return false;
    }

    if (!otpsecret || !wachtwoord)
    {
        var deze = new Perslid(oRs("prs_perslid_key").Value);
        if (wachtwoord && deze.has("WEB_FACFAC"))
        {
            login_fail_reason = L("lcl_login_needs_otpsecret");
            return false;
        }
        doLogin(oRs("prs_perslid_key").Value, params);
    }
    else if (params.otpcode && testotp(oRs("prs_perslid_key").Value, params.otpcode))
        doLogin(oRs("prs_perslid_key").Value, params);
    else // Wordt opgepikt door login_save.asp
        /* global */ otp_user_key = oRs("prs_perslid_key").Value;

    oRs.Close();
    return true;
}

// function SecureSSO
//   Verzorgt de secure Single Signon communicatie protocol
//
// ssoProps
//      strSharedKey: afgesproken shared key
//      onSuccess: functie die aangeroepen wordt bij success
// We kunnen hier nog via twee routes komen: oude stijl (cust/xxxx/sso.asp)
// en nieuwe stijl (xxxx.facilitor.nl?sso=1)
// In het laatste geval zal ssoProps.sso ook 1 of 2 zijn
function SecureSSO(ssoProps)
{
    var strAction, strReturnURL, strKey, strGUID, strCTID
    var strUserName, strDecryptedCode, strControlID, strControlDecryptedCode, strLengthCode
    //'* variables *******************************************************
    //'*******************************************************************
    Response.Buffer=true
    %>
    <HTML>
    <HEAD>
    <script type="text/javascript">
    function fnSubmit() {
      window.document.form.submit();
      return;
    }
    </SCRIPT>
    </HEAD>
    <%
    strReturnURL = getFParam("returnurl", "");
    strReturnURL= strReturnURL.replace("<", "");
    strReturnURL= strReturnURL.replace(">", "");
    strAction = getFParam("action", "");
    if (!strAction && ssoProps.ssoURL) // we zijn begonnen in Facilitor en moeten nog naar de klant
    {
        strReturnURL = ssoProps.ssoURL;
        if (!strReturnURL)
        {
    		__DoLog("Secure SSO login error 0");
    	    Response.Write("Foute aanroep");
    		Response.End;
    	}
        strAction = "requestid";
        Session("SSO_QUERYSTRING") = String(Request.ServerVariables("QUERY_STRING")); // Deze onthouden we
        Session("SSO_URL") = String(Request.ServerVariables("URL")); // Deze onthouden we
    }

    if (strAction == "requestid")
    {
    	// * action = requestid *******************************************
    	%>
    	<BODY  LANGUAGE="javascript" onload="return fnSubmit()">
    	<%
    	if (strReturnURL == "")
    	{
 			__DoLog("Secure SSO login error 1");
    		Response.write("Error: onvoldoende informatie ontvangen.")
    		Response.end
    	}
    	else
        {
    		Response.write("Een moment aub..")
    		strGUID = GetGuid(64)
    		strCTID = GetGuid(strReturnURL.length)
    		// Save GUID
    		Session("GUID") = strGUID;
    		Session("CTID") = strCTID;
    		Session("GUIDEXPIRE") = (new Date()).valueOf()
    		                            + (ssoProps.Timeout?ssoProps.Timeout:30)*1000;
    if (Request.Form("Jumpto").Count>0) // Remember it (old style)
    {
                Session("FirstPage")=""+Request.Form("Jumpto")
    }
    		%>
    		<form action='<%=strReturnURL%>' method="post" name="form" ID="Form1">
    		<input type="hidden" name="guid" value="<%=strGUID%>" ID="Hidden1">
    		<input type="hidden" name="ctid" value="<%=strCTID%>" ID="Hidden2">
    		<%
    	}
    }
    else if (strAction == "processcode")
    {
    	// * action = processcode	*****************************************
    	%>
    	<BODY>
    	<%

    	strUserName = String(Request.form("code"))
    	strControlID = String(Request.form("ctcode"))
    	strLengthCode = Request.form("ltcode")
    	strGUID = Session("GUID")
    	strCTID = Session("CTID")
   		var expire = Session("GUIDEXPIRE");
    	// Clean session memory
    	Session.Contents.Remove("GUID");
	    Session.Contents.Remove("CTID");
  		Session.Contents.Remove("GUIDEXPIRE");
        if (typeof expire == "undefined" || !expire || (new Date()).valueOf() > expire ||
	        typeof strGUID == "undefined" || strUserName == "" || typeof strCTID == "undefined" || strControlID == "")
    	{
    	    Session.Contents.Remove("FirstPage");
    		if (strReturnURL == "")
    		{
    			__DoLog("Secure SSO login error 2");
    			Response.write("Error: onvoldoende informatie ontvangen.")
    			Response.end
    		}
    		else
            {
                // FSN#25537 deze komt erg regelmatig voor maar oorzaak onbekend
    			__Log("Secure SSO login error 3");
    			Response.write("Error: onvoldoende informatie ontvangen.")
    			Response.End;
    			// Response.redirect(strReturnURL) kan oneindige loop geven
    		}
    	}
    	// Convert from ASC chars
    	strUserName = ConvertFromAsc(strUserName)
    	strControlID = ConvertFromAsc(strControlID)
    	// * decrypt ******************************************************
    	//First decoding phase
    	var strKey = (ssoProps.strSharedKey + strGUID).substr(0,strUserName.length);
    	strDecryptedCode = DeCrypt(strUserName)
    	//Second decoding phase
    	var strKey = strGUID.substr(0,strDecryptedCode.length);
    	strDecryptedCode = DeCrypt(strDecryptedCode)
    	// * decrypt Controlkey ********************************************
    	// First decoding phase
    	strKey = (ssoProps.strSharedKey + strCTID).substr(0,strControlID.length)
    	strControlDecryptedCode = DeCrypt(strControlID)
    	// Second decoding phase
    	strKey = strCTID.substr(0,strControlDecryptedCode.length)
    	strControlDecryptedCode = DeCrypt(strControlDecryptedCode)
    	// ltcode strLengthCode
    	if (strControlDecryptedCode == strReturnURL && parseInt(strLengthCode,10) == strDecryptedCode.length)
    	{
    		// For the ASP: User is authenticated, strDecryptedCode contains the validated Domain\Username
    		__Log("SSO Gebruikersnaam = " + strDecryptedCode)
    		if (ssoProps.fnparseName)
    		{
    		    strDecryptedCode = ssoProps.fnparseName(strDecryptedCode)
    		    //Response.write ("<p>Na fnparseName: " + strDecryptedCode)
    		}
		    if (tryLogin(strDecryptedCode, null, { noPassword: true }))
		    {
		        if (ssoProps.fnonSuccess)
		            ssoProps.fnonSuccess(user_key);
		        else // Alles goed!
		        {
		            var sso_qs = Session("SSO_QUERYSTRING")||"";
		            var sso_url = Session("SSO_URL")||"/default.asp";
                    Session.Contents.Remove("SSO_QUERYSTRING");
                    Session.Contents.Remove("SSO_URL");
	                Response.Redirect(sso_url + (sso_qs?"?":"") + sso_qs);
                }
            }
            else
            {   // Automatisch naar het inlogscherm
                __DoLog("Secure SSO login niet gevonden binnen Facilitor: " + strDecryptedCode);
                Response.Redirect(rooturl + "/default.asp");
            }
    	}
    	else
    	{
    		if (strReturnURL == "")
    		{
    			__DoLog("Secure SSO login error 4");
    			Response.write("Error: onvoldoende informatie ontvangen.")
    			Response.end
    		}
    		else
    		{
    			__Log("Secure SSO login error 5");
    		    Response.Write("Decodeer fout");
    			Response.End;
    			Response.redirect(strReturnURL)
    		}
    	}
    }
    else
    {
		__Log("Secure SSO login error 6");
	    Response.Write("Foute aanroep");
		Response.End;
    }
    // * Functions ********************************************************
    function ConvertFromAsc(strAsc)
    {
    	var iCount
    	var iChars
    	var sConvertFromAsc = ""
    	iCount = 0
    	do
    	{
    		iChars = parseInt(strAsc.substr(iCount,1))
    		iCount = iCount + 1
    		sConvertFromAsc = sConvertFromAsc + String.fromCharCode(parseInt(strAsc.substr(iCount,iChars)))
    		iCount = iCount + iChars
    	} while (iCount < strAsc.length);
    	return sConvertFromAsc;
    }

    function GetGuid(iDigits)
    {
        var lsGUID
    	var lsTemp
    	var TypeLib = Server.CreateObject("Scriptlet.TypeLib")
    	var lsTemp = ""
    	do
    	{
    		lsGUID = String(TypeLib.Guid).substr(0, 38)
    		lsTemp = lsTemp + lsGUID.substr(1,8) + lsGUID.substr(10,4) + lsGUID.substr(15,4) + lsGUID.substr(20,4) + lsGUID.substr(25,12)
    	} while (lsTemp.length <  iDigits)
    	TypeLib = null
    	return lsTemp.substr(0,iDigits);
    }

    function DeCrypt(strEncrypted)
    {
        var strChar, iKeyChar, iStringChar, i
        var strDecrypted = "";
        for (i=0; i<strEncrypted.length; i++)
        {
            iKeyChar = strKey.charCodeAt(i);
            iStringChar = strEncrypted.charCodeAt(i);
            iDeCryptChar = iStringChar ^ iKeyChar
            strDecrypted =  strDecrypted + String.fromCharCode(iDeCryptChar);
        }
        return strDecrypted;
    }
    // *********************************************************************
    %>
    </form>
    </BODY>
    </HTML>
<%
    Response.End;
}

var base64s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

function decode_b64(encStr) {
  var bits, decOut = '', i = 0;
  for(; i<encStr.length; i += 4){
    bits =
     (base64s.indexOf(encStr.charAt(i))    & 0xff) <<18 |
     (base64s.indexOf(encStr.charAt(i +1)) & 0xff) <<12 |
     (base64s.indexOf(encStr.charAt(i +2)) & 0xff) << 6 |
      base64s.indexOf(encStr.charAt(i +3)) & 0xff;
    decOut += String.fromCharCode(
     (bits & 0xff0000) >>16, (bits & 0xff00) >>8, bits & 0xff);
    }
  if(encStr.charCodeAt(i -2) == 61)
    undecOut=decOut.substring(0, decOut.length -2);
  else if(encStr.charCodeAt(i -1) == 61)
    undecOut=decOut.substring(0, decOut.length -1);
  else undecOut=decOut;

  return unescape(undecOut);		//line add for chinese char
}

function SimpleSSO()
{
    if (S("use_simple_sso") == 0)
        return;

    var username = String(Session("UID_DEC"));
    __Log('User#1 = '+username);
    // facilitorplace SSO decoded/descripted login?
    // Bij decoded login moet de setting "S("use_simple_sso")" aan staan
    if (username != '' && username!='undefined')
    {
         username = decode_b64(username);
         Session.Contents.Remove("UID_DEC"); // nooit twee keer
        __Log('User#2a = '+username);
    }
    if (username !='' && username!='undefined') {
        // Strip domain name
        while( (i = username.indexOf('\\')) >= 0 ) {
            l = username.length;
            if( i < l-1 ) username = username.substring(i+1,l);
        }
        tryLogin(username, null, { noPassword: true });
    }
}

function IntegratedSSO()
{
    var username = String( Request.ServerVariables("REMOTE_USER") ).toUpperCase();
    __Log('REMOTE_USER = '+username);
    if (username =='' || username=='UNDEFINED')
    {
        username = String( Request.ServerVariables("HTTP_USER") ).toUpperCase();
        __Log('HTTP_USER = '+username);
        if (username =='' || username=='UNDEFINED')
        {
            username = String( Request.ServerVariables("HTTP_LOGIN") ).toUpperCase();
            __Log('HTTP_LOGIN = '+username);
            if (username =='' || username=='UNDEFINED')
            {
                // HTTP_LOGIN, REMOTE_USER or HTTP_USER is not (yet) set. Forcing Windows Authentication
                Response.Status = "401 Unauthorized";
                shared.simpel_page("os_logon is set, trying 401 Unautorized<br>If this page stays, check IIS if integrated authentication is turned on.");
                Response.End(); // Reloads current file
            }
         }
     }
    if (username !='' && username!='UNDEFINED')
    {
        // Strip domain name
        while( (i = username.indexOf('\\')) >= 0 ) {
            l = username.length;
            if( i < l-1 ) username = username.substring(i+1,l);
        }
        tryLogin(username, null, { noPassword: true });
    }
}

/* resultaat:
  { err: "Iets niet goed" }
   of { header: header,
        payload: payload (claim)
        signature: signature
      }
  LET OP: signature is nog niet gevalideerd!
*/
function jwt_decode(token)
{
  // check token
  if (!token) {
    return { err: 'No token supplied' };
  }
  // check segments
  var segments = token.split('.');
  if (segments.length !== 3) {
    return { err:  'Not enough or too many segments' };
  }

  // All segment should be base64
  var result = { headerSeg: segments[0],
                 payloadSeg: segments[1],
                 signature64: segments[2]
               }

  var oCrypto = new ActiveXObject("SLNKDWF.Crypto");

  try
  {
      // base64 decode and parse JSON
      result.header = JSON.parse(oCrypto.base64_decode(result.headerSeg));
      result.payload = JSON.parse(oCrypto.base64_decode(result.payloadSeg));
  }
  catch (s)
  {
    return { err: "Invalid JSON: " + e.description };
  }

  return result;
};

function jwt_verify(decoded_jwt, secret, skew, duration)
{
  skew = skew || 0;
  duration = duration || 0;
  if (decoded_jwt.header.alg != "HS256")
    return { err:  "Only HS256 is supported" };

  var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
  var sig = oCrypto.hex_hmac_sha256(secret, decoded_jwt.headerSeg + "." + decoded_jwt.payloadSeg);
  var sig64 = oCrypto.hex2base64(sig, false, true); // no padding, urlsafe

  var now = new Date().getTime() / 1000;
  if (claim.payload.iat)
  {
    // Support for nbf and exp claims.
    // According to the RFC, they should be in seconds.
    if (claim.payload.nbf && now + skew < claim.payload.nbf ) {
      return { err:  'Token not yet active' };
    }

    if (claim.payload.exp && now > claim.payload.exp + skew) {
      return { err:  'Token expired' };
    }

    // Onze eigen duration/expiration controleren we ook nog
    if (claim.payload.iat + duration < now - skew) {
        __DoLog("Token expired. Now is {0}, got {1}".format(toDateTimeString(new Date(now * 1000), true),
                                                            toDateTimeString(new Date(claim.payload.iat * 1000), true)));
        return { err:  'Token expired' };
    }
    if (claim.payload.iat > now + skew) {
        __DoLog("Token not yet active. Now is {0}, got {1}".format(toDateTimeString(new Date(now * 1000), true),
                                                                   toDateTimeString(new Date(claim.payload.iat * 1000), true)));
        return { err:  'Token not yet active' };
    }
  }
  if (decoded_jwt.signature64 == sig64)
    return { success: true }

  return { err: "Token signature did not verify" };
}

%>
<script language="VBScript" runat="Server">
    '' // Met de beste wil van de wereld kreeg ik dit niet werkend met JScript
    '' // Op de SGF12 moest ik .Expires = '12/31/2012' doen
    '' // Op mijn Vista PC moest ik .Expires = '31/12/2012' doen
    '' // Daarom maar VBScript
    Sub VBexpireCookie(cookiename, interval, number)
        Dim datum
        datum = DateAdd(interval, number, Now)
        Response.Cookies(cookiename).Expires=datum
    End Sub
</script>
