Jump to content

Extending Login Modules


Luke

Recommended Posts

A while back I started creating a login module to integrate a game server into IPB. The game server uses MySQL to store account data and uses a username based login system. Much to my frustration I found that no only did the IPB login module system assume the password was hashed with md5 first and pass that into the login module, but many of the functions assume that the remote system uses the email address as the login method. Many of this has improved in 3.0, but there is still stuff that is missing to make integration possible.

Thankfully a lot of feedback has been taken and some of these things are implemented into 3.1, while maintaining backwards compatibility. But there are some things that are still missing and are vital for username based login integration. Below I will highlight both things that have been fixed in 3.1 (to come, marked in bug tracker as "Fixed"), as well as things that have yet to be.

changePass (FIXED)

This function, in 3.0, currently looks like this: changePass( $email, $password ) (password is hashed with md5). Two issues with this function being that the password is hashed, and there is no username. This was fixed in 3.1 to be changeEmail( $email, $new_pass, $plain_pass, $member ). The "$member" variable contains the whole member array after the original password is verified. (This change maintains backwards compatibility) This works great because not only do you have $plain_pass, but $member contains pretty much everything you'd need. Perhaps in the future this can be changePass( $password, $member ) (not hashed).

changeName and changeEmail (FIXED)

The changeEmail function is missing username, which is especially important with a username only based login scheme. The changeName function is ok because it has both, but it would be a nice to have other information (For example, I added a column called "guildcard" which is an id of the remote user, much like "live_id" is added for windows live).

These were both fixed to be something like this (I'm assuming):

changeEmail( $old_email, $new_email, $member )
changeName( $old_name, $new_name, $email_address, $member )

Which would be perfect, although "changeName" would be better as changeName( $old_name, $new_name, $member ) (but there needs to be backards compatibility from 3.0 I guess)

emailExistsCheck

This function is almost perfect. What it does is makes sure that the email address is unique. When a member registers and types in their email address this function is used to show "This email is taken" both for ajax and when the form is submitted. Same thing happens if you try to change your email address in UCP. The problem is this function (i think) is only used in the ACP when creating a new account, not when editing the email address. (BUG!!! Unless it does, and I missed it...)

nameExistsCheck

This function doesn't exist. I don't understand why it wouldn't. Right now there is absolutely no way to check the remote database to make sure that a username isn't already taken. To me this is a pretty big deal. I've implemented this into my 3.0 installation already, and all I did was pretty much copy and paste it from emailExistsCheck. The only thing I haven't integrated it into is AdminCP, primarily because for some reason the functions for checking username and display name are combined.... Should they be? Seems very weird. Anyway below I will show the code changes I made:

han_login.php

/**

* Check if username is already in use

*

* @access	public

* @param	string 	Email address

* @return	boolean 	Authenticate successful

*/

public function nameExistsCheck( $name )

{

	$this->return_code = 'METHOD_NOT_DEFINED';


	foreach( $this->modules as $k => $obj_reference )

	{

 	if( method_exists( $obj_reference, 'nameExistsCheck' ) )

 	{

 	$obj_reference->nameExistsCheck( $name );

 	$this->return_code 	= $obj_reference->return_code;


 	if( $this->return_code AND !in_array( $this->return_code, array( 'NAME_NOT_IN_USE', 'METHOD_NOT_DEFINED', 'WRONG_AUTH', 'WRONG_OPENID' ) ) )

 	{

 	break;

 	}

 	}

	}


	if( $this->return_code AND !in_array( $this->return_code, array( 'NAME_NOT_IN_USE', 'METHOD_NOT_DEFINED', 'WRONG_AUTH', 'WRONG_OPENID' ) ) )

	{

 	return true;

	}

	else

	{

 	return false;

	}

}

register.php for the registrations (compare modified code)

if( !$uses_name )

{

	$_REQUEST['UserName']		= $_REQUEST['members_display_name'];

	$this->request['UserName']	= $this->request['members_display_name'];

}


/* Load handler... */

require_once( IPS_ROOT_PATH . 'sources/handlers/han_login.php' );

$this->han_login 	= new han_login( $this->registry );

$this->han_login->init();


/* Check the username */

$user_check = IPSMember::getFunction()->cleanAndCheckName( $this->request['UserName'], array(), 'name' );

$disp_check = IPSMember::getFunction()->cleanAndCheckName( $this->request['members_display_name'] ? $this->request['members_display_name'] : $this->request['UserName'], array(), 'members_display_name' );


if( is_array( $user_check['errors'] ) && count( $user_check['errors'] ) )

{

	foreach( $user_check['errors'] as $key => $error )

	{

		$form_errors[ $key ][]	= $error;

	}

}


if( is_array( $disp_check['errors'] ) && count( $disp_check['errors'] ) )

{

	foreach( $disp_check['errors'] as $key => $error )

	{

		if( !$this->settings['auth_allow_dnames'] AND !( is_array( $user_check['errors'] ) && count( $user_check['errors'] ) ) )

		{

			$key	= 'username';

		}


		$form_errors[ $key ][]	= $error;

	}

}


$this->han_login->nameExistsCheck( $this->request['UserName'] );


if( $this->han_login->return_code AND $this->han_login->return_code != 'METHOD_NOT_DEFINED' AND $this->han_login->return_code != 'NAME_NOT_IN_USE' )

{

	$form_errors['username'][$this->lang->words['reg_error_username_taken']] = $this->lang->words['reg_error_username_taken'];

}


/* CHECK 1: Any errors (missing fields, etc)? */

if( count( $form_errors ) )

{

	$this->registerForm( $form_errors );

	return;

}


/* Is this email addy taken? */

if( IPSMember::checkByEmail( $in_email ) == TRUE )

{

	$form_errors['email'][$this->lang->words['reg_error_email_taken']] = $this->lang->words['reg_error_email_taken'];

}


$this->han_login->emailExistsCheck( $in_email );


if( $this->han_login->return_code AND $this->han_login->return_code != 'METHOD_NOT_DEFINED' AND $this->han_login->return_code != 'EMAIL_NOT_IN_USE' )

{

	$form_errors['email'][$this->lang->words['reg_error_email_taken']] = $this->lang->words['reg_error_email_taken'];

}

And then the ajax register.php (I split the function into two - need to update doExecute)

/**

 * Check the name

 *

 * @access	public

 * @return	void		[Outputs to screen]

 */

public function checkName()

{

	//-----------------------------------------

	// INIT

	//-----------------------------------------


	$this->registry->class_localization->loadLanguageFile( array( 'public_register' ) );

	$name	= '';


	if( is_string($_POST['name']) )

	{

		$name	= strtolower( trim( rawurldecode( $_POST['name'] ) ) );

	}


	if( !$name )

	{

		$this->returnString( sprintf( ipsRegistry::getClass( 'class_localization' )->words['reg_error_no_name'], ipsRegistry::$settings['max_user_name_length'] ) );

	}


	//-----------------------------------------

	// Load handler...

	//-----------------------------------------


	require_once( IPS_ROOT_PATH . 'sources/handlers/han_login.php' );

	$han_login		= new han_login( $this->registry );

	$han_login->init();


	if( $han_login->nameExistsCheck( $name ) )

	{

		$this->returnString('found');

	}

	else

	{		

		/* Check the username */

		$user_check = IPSMember::getFunction()->cleanAndCheckName( $name, array(), 'name' );


		if( is_array( $user_check['errors']['username'] ) && count( $user_check['errors']['username'] ) )

		{

			$this->returnString( $user_check['errors']['username'][0] );

		}

		else if( $user_check['errors']['username'] )

		{

			$this->returnString( $user_check['errors']['username'] );

		}

		else

		{

			$this->returnString('notfound');

		}

	}

}


/**

 * Check the display name

 *

 * @access	public

 * @return	void		[Outputs to screen]

 */

public function checkDisplayName()

{

	//-----------------------------------------

	// INIT

	//-----------------------------------------


	$this->registry->class_localization->loadLanguageFile( array( 'public_register' ) );

	$name	= '';


	if( is_string($_POST['name']) )

	{

		$name	= strtolower( trim( rawurldecode( $_POST['name'] ) ) );

	}


	if( !$name )

	{

		$this->returnString( sprintf( ipsRegistry::getClass( 'class_localization' )->words['reg_error_no_name'], ipsRegistry::$settings['max_user_name_length'] ) );

	}


	/* Check the display name */

	$user_check = IPSMember::getFunction()->cleanAndCheckName( $name, array(), 'members_display_name' );


	if( is_array( $user_check['errors']['members_display_name'] ) && count( $user_check['errors']['members_display_name'] ) )

	{

		$this->returnString( $user_check['errors']['members_display_name'][0] );

		return;

	}

	else if( $user_check['errors']['members_display_name'] )

	{

		$this->returnString( $user_check['errors']['members_display_name'] );

	}

	else

	{

		$this->returnString('notfound');

	}

}



And that's pretty much it. Maybe it could be done better... And like I said, it needs to be done in AdminCP as well - when creating an account AND changing the username.

userValidated

Another thing that doesn't exist. Would be nice to run a function like this when a user succesfully validates, whether it be admin validation or email validation. Also passing what type of validation took place would be nice. I would like to see something like:

userValidated( $type=email|admin, $member )

With our game server we have a column called "isactive" for this purpose. Would be nice to make this "0" when someone registers and make it "1" when the board validates their account. Right now I have to keep it on "1"... So someone can play the game without validating, but they have to validate on the forum... Ideally I'd like to be able to not let them in until they validate the account.


Pretty much what I'm trying to do is use IPB for all user management, which is username based. Trying to get some of the problems with the login auth modules irorned out for 3.1. That way I can make those changes in my 3.0 installation, and just upgrade to 3.1 without making any changes. (So when you do add something, please tell me what the parameters are).


Thanks :)

Link to comment
Share on other sites

We added the nameCheckExists function:


	/**

	 * Check if the username is already in use

	 *

	 * @access	public

	 * @param	string		User Name

	 * @param	string		Array of member data

	 * @param	srting		Field to check, members_l_username or members_l_display_name

	 * @return	boolean		Authenticate successful

	 */

  	public function nameExistsCheck( $nameToCheck, $memberData, $fieldToCheck='members_l_username' )

Link to comment
Share on other sites

Cool! Thanks :D!!

Is the function applied when adding/editing an account in the AdminCP and does the emailExistsCheck get applied when editing the email address?

Any chance on a "userValidated" function? (I don't have to have it, but it would allow us to "validate" accounts in our remote database)

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...