Source: Authentication.js

/**
 * This class provides the core authentication functionality.
 * @param server Server configuration object.
 * @param {string} server.Address URI root path to server.
 * @param {boolean} server.Secure Whether to use a secure connection during the authentication or not.<br />
 * @param events Event configuration object.
 * @param {Authentication~AuthenticatedCallback} events.Authenticated Callback called when Authentication class has confirmed authentication.
 * @param {Authentication~RequireLoginCallback} events.RequireLogin Callback called when Authentication class has determined login is required.
 * @constructor
 */
function Authentication(server, events) {
    this.Dialog = null;
    this.DialogName = '';
    this.Server = server;
    this.Server.Events.InfoChanged.push(this.Socket_InfoChanged);
    this.Authenticated = false;
    this.SessionKey = getCookie('sk');
    this.SavedUsername = getCookie('su');
    if (this.SavedUsername == null) { this.SavedUsername = ''; }
    this.SaveUsername = ((this.SavedUsername != null) && (this.SavedUsername.length > 0));
    this.SavePassword = (getCookie('sp') == '1');
    if (this.SessionKey == null) { this.SessionKey = ''; }
    this.UserID = 0;
    this.Username = '';
    if (this.Server.Secure === true) {
        this.Socket = io.connect(this.Server.Address + '/authentication', { secure: true });
    } else {
        this.Socket = io.connect(this.Server.Address + '/authentication');
    }
    this.Socket.socket.Authentication = this;
    this.Socket.on('authenticated', this.Socket_Authenticated);
    this.Socket.on('connect', this.Socket_Connect);
    this.Socket.on('disconnect', this.Socket_Disconnect);
    this.Socket.on('info', this.Socket_Info);
    this.Socket.on('invalid login', this.Socket_InvalidLogin);
    this.Socket.on('logged out', this.Socket_LoggedOut);
    this.Socket.on('unauthorized', this.Socket_Unauthorized);
    this.Events = {
        Authenticated: (events.hasOwnProperty('Authenticated') && (events.hasOwnProperty('Authenticated') != null) ? [events.Authenticated] : []),
        RequireLogin: (events.hasOwnProperty('RequireLogin') && (events.hasOwnProperty('RequireLogin') != null) ? [events.RequireLogin] : [])
    };
}

/**
 * Disposes of the Authentication class instance and frees up allocated resources.
 * @function
 * @name Authentication#Dispose
 */
function Authentication_Dispose() {
    this.RemoveDialog();
}

/**
 * Displays a login dialog, creating it first if necessary.
 * @function
 * @name Authentication#DisplayLogin
 */
function Authentication_DisplayLogin() {
    if ((this.Dialog != null) && (this.DialogName != 'login')) { this.RemoveDialog(); }
    if (this.DialogName != 'login') {
        Authentication.BGTable.css('min-height', '200px');
        Authentication.TDDialogSpace.append('<form id="loginform" name="authenticationform" action="" method="POST"><table class="authenticationdialog" border="0" cellpadding="0" cellspacing="0" style="width: 340px; height: 200px;"><tr><th align="center" valign="middle"><h1 class="authenticationdialogtitle">' + (this.Server.SiteTitle.length > 0 ? this.Server.SiteTitle + ' l' : 'L') + 'ogin</h1></th></tr><tr><td align="center" valign="middle"><table border="0" cellpadding="0" cellspacing="0" style="border: 0px; margin: 0px; padding: 0px; width: 320px; height: 80px;"><tr><th align="left" valign="middle"><label id="labUsername" name="labUsername" for="txtUsername">Username:</label></th><td align="right" valign="middle"><input id="txtUsername" name="txtUsername" type="text" style="width: 220px;" value="' + this.SavedUsername + '" /></td></tr><tr><th align="left" valign="middle"><label id="labPassword" name="labPassword" for="pwdPassword">Password:</label></th><td align="right" valign="middle"><input id="pwdPassword" name="pwdPassword" type="password" style="width: 220px;" /></td></tr></table></td></tr><tr><td class="authenticationdialogerror"></td></tr><tr><td align="center" valign="middle"><table border="0" cellpadding="0" cellspacing="0" style="border: 0px; margin: 0px; padding: 0px; width: 320px; height: 80px;"><tr><td align="center" valign="middle"><table border="0" cellpadding="0" cellspacing="0" style="border: 0px; margin: 0px; padding: 0px; width: 240px; height: 60px;"><tr><td align="left" valign="middle"><label id="labSaveUsername" name="labSaveUsername" for="chkSaveUsername"><input id="chkSaveUsername" name="chkSaveUsername" type="checkbox" value="1"' + (this.SaveUsername === true ? ' CHECKED' : '') + ' />&nbsp;Save&nbsp;Username</label></td></tr><tr><td align="left" valign="middle"><label id="labSavePassword" name="labSavePassword" for="chkSavePassword"><input id="chkSavePassword" name="chkSavePassword" type="checkbox" value="1"' + (this.SavePassword === true ? ' CHECKED' : '') + ' />&nbsp;Save&nbsp;Password</label></td></td></tr></table></td><td align="center" valign="middle"><input id="subLogin" name="subLogin" type="submit" value="Login" style="width: 80px; height: 60px;" /></td></tr></table></td></tr></table></form>');
        this.Dialog = $('form', this.TDDialogSpace).last();
        this.DialogName = 'login';
        Authentication.Dialog = this.Dialog;
        Authentication.DialogName = this.DialogName;
        this.Dialog.bind('submit', this, function (event) {
            try {
                event.data.SavedUsername = Authentication.Focusables[0].attr('value');
                event.data.SaveUsername = Authentication.Focusables[2][0].checked;
                event.data.SavePassword = Authentication.Focusables[3][0].checked;
                event.data.Login(event.data.SavedUsername, Authentication.Focusables[1].attr('value'), event.data.SaveUsername, event.data.SavePassword);
                event.data.RemoveDialog();
            } catch (ex) { }
            return (false);
        });
        Authentication.Focusables = [
            $('#txtUsername', Authentication.Dialog),
            $('#pwdPassword', Authentication.Dialog),
            $('#chkSaveUsername', Authentication.Dialog),
            $('#chkSavePassword', Authentication.Dialog),
            $('#subLogin', Authentication.Dialog)
        ];
        if ((this.SavedUsername != null) && (this.SavedUsername.length > 0)) {
            Authentication.Focusables[0].attr('value', this.SavedUsername);
            Authentication.Focusables[1].focus();
        } else {
            Authentication.Focusables[0].focus();
        }
    }
    this.Dialog.show();
    Authentication.Container.show();
}

/**
 * Sends login message to server.
 * @function
 * @name Authentication#Login
 * @param {string} username
 * @param {string} password
 * @param {boolean} saveUsername
 * @param {boolean} savePassword
 */
function Authentication_Login(username, password, saveUsername, savePassword) {
    this.Socket.emit('login', { Username: username, Password: password, SaveUsername: (saveUsername === true), SavePassword: (savePassword === true) });
}

/**
 * Sends logout message to server.
 * @function
 * @name Authentication#Logout
 */
function Authentication_Logout() {
    this.Socket.emit('logout', { SessionKey: this.SessionKey });
}

/**
 * Removes the login dialog and clears references to it.
 * @function
 * @name Authentication#RemoveDialog
 */
function Authentication_RemoveDialog() {
    if (this.Dialog != null) {
        if (Authentication.Dialog === this.Dialog) { Authentication.Dialog = null; }
        this.Dialog.remove();
        this.Dialog = null;
        this.DialogName = '';
    }
}

/**
 * Server authentication message handling.
 * @function
 * @name Authentication#Socket_Authenticated
 * @param parameters Parameter information object.
 * @param {boolean} parameters.SavePassword Determines whether or not to save the password used during this authentication request.
 * @param {boolean} parameters.SaveUsername Determines whether or not to save the username used during this authentication request.
 * @param {string} parameters.SessionKey The allocated session key for the handled authentication request.
 * @param {number} parameters.UserID The user id of the authenticated user.
 * @param {string} parameters.Username The username of the authenticated user.
 */
function Authentication_Socket_Authenticated(parameters) {
    console.log('Server[' + this.socket.Authentication.Server.Address + '].Authentication Authenticated');
    if (parameters.hasOwnProperty('SaveUsername')) { this.socket.Authentication.SaveUsername = (parameters.SaveUsername === true); }
    if (parameters.hasOwnProperty('Username') && (this.socket.Authentication.SaveUsername === true)) { setCookie('su', parameters.Username, 30); }
    if (parameters.hasOwnProperty('SavePassword')) { this.socket.Authentication.SavePassword = (parameters.SavePassword === true); }
    if (parameters.hasOwnProperty('SessionKey') && (this.socket.Authentication.SavePassword === true)) {
        setCookie('sk', parameters.SessionKey, 30);
        if (parameters.hasOwnProperty('Username')) { this.socket.Authentication.SavedUsername = parameters.Username; }
        else { this.socket.Authentication.SavedUsername = ''; }
    } else {
        setCookie('sk', parameters.SessionKey, 0);
        this.socket.Authentication.SavedUsername = '';
    }
    if (this.socket.Authentication.SavePassword === true) { setCookie('sp', '1', 30); } else { setCookie('sp', '0', 30); }
    this.socket.Authentication.Authenticated = true;
    this.socket.Authentication.SessionKey = parameters.SessionKey;
    this.socket.Authentication.UserID = parameters.UserID;
    this.socket.Authentication.Username = parameters.Username;
    this.socket.Authentication.UserID = (parameters.hasOwnProperty('UserID') ? parameters.UserID : 0);
    this.socket.Authentication.Username = (parameters.hasOwnProperty('Username') ? parameters.Username : '');
    if (this.socket.Authentication.Events.Authenticated.length > 0) {
        for (var i = 0; i < this.socket.Authentication.Events.Authenticated.length; i++) {
            this.socket.Authentication.Events.Authenticated[i](this.socket.Authentication);
        }
    }
}

/**
 * The socket connect event handler will process the state of the Authentication class and either send login information or session key if appropriate.  May be called multiple times to resume connections.
 * @function
 * @name Authentication#Socket_Connect
 */
function Authentication_Socket_Connect() {
    console.log('Server[' + this.socket.Authentication.Server.Address + '].Authentication Connected');
    if (this.socket.Authentication.SessionKey.length == 64) {
        console.log('Server[' + this.socket.Authentication.Server.Address + '].Authentication Attempting login with session key...');
        this.emit('login', { SessionKey: this.socket.Authentication.SessionKey, SaveUsername: this.SaveUsername, SavePassword: this.SavePassword });
    } else {
        console.log('Server[' + this.socket.Authentication.Server.Address + '].Authentication Login Required.');
        this.socket.Authentication.DisplayLogin();
        if (this.socket.Authentication.Events.RequireLogin.length > 0) {
            for (var i = 0; i < this.socket.Authentication.Events.RequireLogin.length; i++) {
                this.socket.Authentication.Events.RequireLogin[i](this.socket.Authentication);
            }
        }
    }
}

/**
 * The socket disconnect event handler will log the disconnection event.
 * @function
 * @name Authentication#Socket_Disconnect
 */
function Authentication_Socket_Disconnect() {
    console.log('Server[' + this.socket.Authentication.Server.Address + '].Authentication Disconnected');
}

/**
 * Handles socket events signaling a change in authentication dialog info.
 * @function
 * @name Authentication#Socket_InfoChanged
 * @param {Object} server Server object containing changed information.
 */
function Server_Socket_InfoChanged(server) {
    if (server.Socket.socket.Authentication.Dialog != null) {
        switch (server.Socket.socket.Authentication.DialogName) {
            case 'login':
                $('.authenticationdialogtitle', server.Socket.socket.Authentication.Dialog).text((server.Socket.socket.Server.SiteTitle.length > 0 ? server.Socket.socket.Server.SiteTitle + ' l' : 'L') + 'ogin');
                break;
        }
    }
}

/**
 * Processes an invalid login attempt as reported by the server.
 * @function
 * @name Authentication#Socket_InvalidLogin
 */
function Server_Socket_InvalidLogin() {
    console.log('Server[' + this.socket.Authentication.Server.Address + '].Authentication Invalid Login');
    if ((this.socket.Authentication.Dialog != null) && (this.socket.Authentication.DialogName == 'login')) {
        $('td.authenticationdialogerror', this.socket.Authentication.Dialog).text('Invalid username or password.');
    }
}

/**
 * Handles a message from the server stating the authentication session has been logged out.
 * @function
 * @name Authentication#Socket_LoggedOut
 */
function Authentication_Socket_LoggedOut() {
    console.log('Server[' + this.socket.Authentication.Server.Address + '].Authentication Logged Out');
    this.socket.Authentication.DisplayLogin();
    this.socket.Authentication.SessionKey = '';
    this.socket.Authentication.UserID = 0;
    this.socket.Authentication.Username = '';
    setCookie('sk', '');
    if (this.socket.Authentication.Events.RequireLogin.length > 0) {
        for (var i = 0; i < this.socket.Authentication.Events.RequireLogin.length; i++) {
            this.socket.Authentication.Events.RequireLogin[i](this.socket.Authentication);
        }
    }
}

/**
 * Handles a message from the server stating that the authorization attempt was unauthorized.
 * @function
 * @name Authentication#Socket_Unauthorized
 */
function Authentication_Socket_Unauthorized() {
    console.log('Server[' + this.socket.Authentication.Server.Address + '].Authentication Unauthorized');
    this.socket.Authentication.DisplayLogin();
    this.socket.Authentication.SessionKey = '';
    this.socket.Authentication.UserID = 0;
    this.socket.Authentication.Username = '';
    if (this.socket.Authentication.Events.RequireLogin.length > 0) {
        for (var i = 0; i < this.socket.Authentication.Events.RequireLogin.length; i++) {
            this.socket.Authentication.Events.RequireLogin[i](this.socket.Authentication);
        }
    }
}
Authentication.prototype.constructor = Authentication;
Authentication.prototype.Dispose = Authentication_Dispose;
Authentication.prototype.DisplayLogin = Authentication_DisplayLogin;
Authentication.prototype.Login = Authentication_Login;
Authentication.prototype.Logout = Authentication_Logout;
Authentication.prototype.RemoveDialog = Authentication_RemoveDialog;
Authentication.prototype.Socket_Authenticated = Authentication_Socket_Authenticated;
Authentication.prototype.Socket_Connect = Authentication_Socket_Connect;
Authentication.prototype.Socket_Disconnect = Authentication_Socket_Disconnect;
Authentication.prototype.Socket_InfoChanged = Server_Socket_InfoChanged;
Authentication.prototype.Socket_InvalidLogin = Server_Socket_InvalidLogin;
Authentication.prototype.Socket_LoggedOut = Authentication_Socket_LoggedOut;
Authentication.prototype.Socket_Unauthorized = Authentication_Socket_Unauthorized;
Authentication.Parent = null;

/**
 * Handles a message from the server stating that the authorization attempt was unauthorized.
 * @static
 * @function
 * @name Authentication#Initialize
 * @param {JQuery} parent The object into which authentication dialogs may be injected.
 */
Authentication.Initialize = function (parent) {
    this.Parent = parent;
    this.Parent.append('<div class="authentication"><table border="0" cellpadding="0" cellspacing="0"><tr><td align="center" valign="middle"></td></tr></table></div>');
    this.Container = $('div.authentication', this.Parent).last();
    this.TDCenter = $('td', this.Container).last();
    this.TDCenter.append('<table class="bg" border="0" cellpadding="0" cellspacing="0" style=""><tr><td align="center" valign="middle"></td></tr></table>');
    this.BGTable = $('table.bg', this.TDCenter).last();
    this.TDDialogSpace = $('td', this.BGTable).last();
    this.Dialog = null;
    this._focusables = [ ];
    this.focusIndex = -1;
    this.onFocusableBlur = function (event) {
        setTimeout(function () {
            if (Authentication.focusIndex >= 0) {
                try { Authentication._focusables[Authentication.focusIndex].focus(); } catch (ex) { }
            }
        }, 1);
    };
    this.onFocusableFocus = function (event) {
        for (var i = 0; i < Authentication._focusables.length; i++) {
            if (Authentication._focusables[i][0] === event.target) {
                Authentication.focusIndex = i;
                return;
            }
        }
    };
    Object.defineProperty(Authentication, 'Focusables', {
        get: function () {
            return (Authentication._focusables);
        },
        set: function (value) {
            Authentication.focusIndex = -1;
            for (var i = 0; i < Authentication._focusables.length; i++) {
                Authentication._focusables[i].unbind('blur', Authentication.onFocusableBlur);
                Authentication._focusables[i].unbind('focus', Authentication.onFocusableFocus);
            }
            Authentication._focusables = value;
            for (var i = 0; i < Authentication._focusables.length; i++) {
                Authentication._focusables[i].bind('focus', Authentication.onFocusableFocus);
                Authentication._focusables[i].bind('blur', Authentication.onFocusableBlur);
            }
        }
    });
};

/**
 * Callback used by Authentication class when it has confirmed authentication.
 * @callback Authentication~AuthenticatedCallback
 * @param {Authentication} authentication The Authentication class used during the authentication.
 */
/**
 * Callback used by Authentication class when it has determined that login is required.
 * @callback Authentication~RequireLoginCallback
 * @param {Authentication} authentication The Authentication class which has determined authentication to be required.
 */