Luke Posted February 18, 2010 Posted February 18, 2010 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 :)
Josh Posted February 23, 2010 Posted February 23, 2010 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' )
Luke Posted February 23, 2010 Author Posted February 23, 2010 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)
Josh Posted February 25, 2010 Posted February 25, 2010 I don't recall the details now, but I did check into emailExistsCheck and it was being applied correctly everywhere that I could see. I don't think we'll be adding userValidated in this release, maybe in the future though.
Luke Posted February 25, 2010 Author Posted February 25, 2010 Ok, thanks josh :)! I'll make sure to thoroughly test it for the beta (when it comes) and let you know if I have any problems. If all I have to add myself is userValidated, I'll be doing pretty good :D
Recommended Posts
Archived
This topic is now archived and is closed to further replies.