Merge pull request #2218 from kijin/pr/member-auth-mail

ID/PW 찾기 방법 개선
This commit is contained in:
Kijin Sung 2023-11-29 23:52:30 +09:00 committed by GitHub
commit b60426f984
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 248 additions and 45 deletions

View file

@ -42,7 +42,7 @@
<action name="procMemberLogin" type="controller" route="login" />
<action name="procMemberFindAccount" type="controller" method="GET|POST" ruleset="findAccount" />
<action name="procMemberFindAccountByQuestion" type="controller" method="GET|POST" />
<action name="procMemberAuthAccount" type="controller" method="GET|POST" />
<action name="procMemberAuthAccount" type="controller" method="GET|POST" route="auth/$member_srl/$auth_key" />
<action name="procMemberAuthEmailAddress" type="controller" method="GET|POST" />
<action name="procMemberResendAuthMail" type="controller" ruleset="resendAuthMail" />
<action name="procMemberSendVerificationSMS" type="controller" />
@ -50,6 +50,7 @@
<action name="procMemberModifyInfoBefore" type="controller" permission="member" ruleset="recheckedPassword" />
<action name="procMemberModifyInfo" type="controller" permission="member" />
<action name="procMemberModifyPassword" type="controller" permission="member" ruleset="modifyPassword" />
<action name="procMemberResetPassword" type="controller" ruleset="resetPassword" />
<action name="procMemberModifyEmailAddress" type="controller" permission="member" ruleset="modifyEmailAddress" />
<action name="procMemberLeave" type="controller" permission="member" ruleset="leaveMember" />
<action name="procMemberInsertProfileImage" type="controller" permission="member" ruleset="insertProfileImage" />

View file

@ -165,6 +165,7 @@ $lang->msg_checked_file_is_deleted = '%d attached file(s) is(are) deleted.';
$lang->msg_find_account_title = 'Account Info';
$lang->msg_find_account_info = 'This is requested account info.';
$lang->msg_find_account_comment = 'Your password will be changed to the one above if you click the link below.<br />Please change the password again as soon as possible after you are able to log in.<br />Your password will remain unchanged until you click the link below.';
$lang->msg_find_account_comment_v2 = 'Click the link below to set a new password for your account, even if you don\'t remember your current password.';
$lang->msg_confirm_account_title = 'Rhymix Account Activation';
$lang->title_modify_email_address = 'This letter is sent for a confirmation of the changing e-mail address.';
$lang->msg_confirm_account_info = 'This is your account information:';
@ -217,6 +218,9 @@ $lang->cmd_password_hashing_algorithm = 'Password Hashing Algorithm';
$lang->cmd_password_hashing_work_factor = 'Password Hashing Work Factor';
$lang->cmd_password_hashing_auto_upgrade = 'Auto-upgrade Hashing Algorithm';
$lang->cmd_password_change_invalidate_other_sessions = 'Sign out other devices on password change';
$lang->cmd_password_reset_method = 'Password Reset';
$lang->cmd_password_reset_method_v1 = 'Email random password';
$lang->cmd_password_reset_method_v2 = 'Email link to password reset page (recommended)';
$lang->cmd_login_invalidate_other_sessions = 'Sign out other devices';
$lang->password_strength_low = 'low';
$lang->password_strength_normal = 'normal';
@ -226,6 +230,7 @@ $lang->about_password_hashing_algorithm = 'Choose how to encrypt (hash) password
$lang->about_password_hashing_work_factor = 'Higher work factors result in more security, but at the cost of delays at login and increased server load.<br />With bcrypt, each step takes twice as long as the step below. Similar conversions are applied to pbkdf2 and argon2id.';
$lang->about_password_hashing_auto_upgrade = 'Passwords not encrypted using the method configured above will be automatically converted to the configured method at next login.';
$lang->about_password_change_invalidate_other_sessions = 'Sign out all other devices (browsers) when a member changes the password.';
$lang->about_password_reset_method = 'Select how to assign a new password in the Find Member Account feature.';
$lang->about_login_invalidate_other_sessions = 'Allow login from only one device. Previously used machines will be signed out automatically.';
$lang->about_password_strength['low'] = 'the password should be longer, at least four characters';
$lang->about_password_strength['normal'] = 'the password should be at least six characters, and should have at least one alpha character and numeric characters';
@ -380,7 +385,6 @@ $lang->spammer_move_to_trash = 'Move to trash';
$lang->msg_spammer_complete = 'Completed.';
$lang->nick_name_before_changing = 'Old nickname';
$lang->nick_name_after_changing = 'New nickname';
$lang->cmd_login_browser_info = 'Browser Information';
$lang->cmd_login_device_info = 'Device Information';
$lang->cmd_initial_registration = 'Registered';
@ -393,3 +397,5 @@ $lang->scrap_folder_rename = 'Rename';
$lang->scrap_folder_delete = 'Delete';
$lang->member_unauthenticated = 'Unauthenticated';
$lang->member_number = 'Member identification number';
$lang->msg_change_after_click = 'Change after clicking link below';
$lang->msg_password_changed = 'Your password has been changed.';

View file

@ -167,6 +167,7 @@ $lang->msg_checked_file_is_deleted = '%d개의 첨부 파일이 삭제되었습
$lang->msg_find_account_title = '아이디/비밀번호 정보입니다.';
$lang->msg_find_account_info = '요청한 계정 정보는 아래와 같습니다.';
$lang->msg_find_account_comment = '아래 링크를 클릭하면 위에 적힌 비밀번호로 변경됩니다.<br />로그인 후 다른 비밀번호로 변경해 주시기 바랍니다.<br />링크를 클릭하지 않으면 비밀번호가 변경되지 않습니다.';
$lang->msg_find_account_comment_v2 = '아래의 링크를 클릭하면 현재 비밀번호를 모르더라도 새로운 비밀번호로 변경할 수 있습니다.';
$lang->msg_confirm_account_title = '가입 인증 메일입니다.';
$lang->title_modify_email_address = '이메일주소 변경 요청 확인 메일입니다.';
$lang->msg_confirm_account_info = '가입한 계정 정보는 아래와 같습니다.';
@ -219,6 +220,9 @@ $lang->cmd_password_hashing_algorithm = '비밀번호 암호화 알고리즘';
$lang->cmd_password_hashing_work_factor = '비밀번호 암호화 소요시간';
$lang->cmd_password_hashing_auto_upgrade = '알고리즘 자동 업그레이드';
$lang->cmd_password_change_invalidate_other_sessions = '비번 변경시 다른 기기 로그아웃';
$lang->cmd_password_reset_method = 'ID/PW 찾기 방법';
$lang->cmd_password_reset_method_v1 = '랜덤 비밀번호 전달';
$lang->cmd_password_reset_method_v2 = '비밀번호 변경 화면 링크 전달 (권장)';
$lang->cmd_login_invalidate_other_sessions = '다른 기기 로그아웃';
$lang->password_strength_low = '낮음';
$lang->password_strength_normal = '보통';
@ -228,6 +232,7 @@ $lang->about_password_hashing_algorithm = '회원들의 비밀번호를 DB에
$lang->about_password_hashing_work_factor = '오래 걸리는 암호화 방식일수록 안전하지만, 로그인이 오래 걸리거나 서버 부하가 상승할 수 있습니다.<br />소요시간은 bcrypt 기준 각 단계마다 2배씩 늘어나며, pbkdf2와 argon2id에서도 유사한 기준으로 적용됩니다.';
$lang->about_password_hashing_auto_upgrade = '위에서 설정된 것과 다른 방법으로 암호화된 비밀번호가 있으면 다음 로그인시 설정된 알고리즘으로 자동 변환합니다.';
$lang->about_password_change_invalidate_other_sessions = '비밀번호를 변경하면 현재 기기(브라우저)를 제외한 모든 로그인이 풀리도록 합니다.';
$lang->about_password_reset_method = 'ID/PW 찾기 기능 사용시, 새 비밀번호로 변경하는 방법을 선택합니다.';
$lang->about_login_invalidate_other_sessions = '한 번에 하나의 기기에서만 로그인할 수 있도록 합니다.';
$lang->about_password_strength['low'] = '비밀번호는 4자 이상이어야 합니다.';
$lang->about_password_strength['normal'] = '비밀번호는 6자리 이상이어야 하며 영문과 숫자를 반드시 포함해야 합니다.';
@ -396,3 +401,5 @@ $lang->scrap_folder_rename = '이름 변경';
$lang->scrap_folder_delete = '삭제';
$lang->member_unauthenticated = '미인증';
$lang->member_number = '회원 번호';
$lang->msg_change_after_click = '아래 링크 클릭 후 변경 가능';
$lang->msg_password_changed = '비밀번호가 변경되었습니다.';

View file

@ -241,6 +241,7 @@ class MemberAdminController extends Member
'password_hashing_work_factor',
'password_hashing_auto_upgrade',
'password_change_invalidate_other_sessions',
'password_reset_method',
'allow_nickname_change',
'update_nickname_log',
'nickname_symbols',
@ -330,7 +331,7 @@ class MemberAdminController extends Member
{
$args->password_hashing_auto_upgrade = 'N';
}
$args->password_reset_method = intval($args->password_reset_method);
if(!in_array($args->nickname_symbols, ['Y', 'N', 'LIST']))
{
$args->nickname_symbols = 'Y';

View file

@ -40,6 +40,7 @@ class Member extends ModuleObject
$config = MemberModel::getMemberConfig();
$config->mid = 'member';
$config->force_mid = true;
$config->password_reset_method = 2;
$this->createMid($config->mid);
$oModuleController->insertModuleConfig('member', $config);
}
@ -170,6 +171,12 @@ class Member extends ModuleObject
if(!$oDB->isColumnExists('member_devices', 'device_token_type')) return true;
if(!$oDB->isColumnExists('member_devices', 'last_active_date')) return true;
// Check member_auth_mail table
if(!$oDB->isColumnExists('member_auth_mail', 'auth_type')) return true;
if(!$oDB->isIndexExists('member_auth_mail', 'unique_auth_key')) return true;
if(!$oDB->isIndexExists('member_auth_mail', 'idx_member_srl')) return true;
if($oDB->isIndexExists('member_auth_mail', 'unique_key')) return true;
// Update status column
$output = executeQuery('member.getDeniedAndStatus');
if ($output->data->count)
@ -383,6 +390,25 @@ class Member extends ModuleObject
$oDB->query("UPDATE member_devices SET last_active_date = regdate WHERE last_active_date = ''");
}
// Check member_auth_mail table
if(!$oDB->isColumnExists('member_auth_mail', 'auth_type'))
{
$oDB->addColumn('member_auth_mail', 'auth_type', 'varchar', '20', 'password_v1', true, 'new_password');
$oDB->query("UPDATE member_auth_mail SET auth_type = 'signup' WHERE is_register = 'Y'");
}
if(!$oDB->isIndexExists('member_auth_mail', 'unique_auth_key'))
{
$oDB->addIndex('member_auth_mail', 'unique_auth_key', ['auth_key'], true);
}
if(!$oDB->isIndexExists('member_auth_mail', 'idx_member_srl'))
{
$oDB->addIndex('member_auth_mail', 'idx_member_srl', ['member_srl']);
}
if($oDB->isIndexExists('member_auth_mail', 'unique_key'))
{
$oDB->dropIndex('member_auth_mail', 'unique_key');
}
// Update status column
$output = executeQuery('member.getDeniedAndStatus');
if ($output->data->count)

View file

@ -1146,23 +1146,24 @@ class MemberController extends Member
*
* @return void|Object (void : success, Object : fail)
*/
function procMemberModifyPassword()
public function procMemberModifyPassword()
{
if (!Context::get('is_logged'))
$config = MemberModel::getMemberConfig();
$vars = Context::getRequestVars();
if (!$this->user->member_srl)
{
throw new Rhymix\Framework\Exceptions\MustLogin;
}
// Extract the necessary information in advance
$current_password = trim(Context::get('current_password'));
$password = trim(Context::get('password1'));
$current_password = trim($vars->current_password);
$password = trim($vars->password1);
// Get information of logged-in user
$logged_info = Context::get('logged_info');
$member_srl = $logged_info->member_srl;
$member_srl = $this->user->member_srl;
$member_info = MemberModel::getMemberInfoByMemberSrl($member_srl);
// Verify the cuttent password
// Verify the current password
if (!MemberModel::isValidPassword($member_info->password, $current_password, $member_srl))
{
throw new Rhymix\Framework\Exception('invalid_current_password');
@ -1185,16 +1186,91 @@ class MemberController extends Member
}
// Log out all other sessions.
$member_config = ModuleModel::getModuleConfig('member');
if ($member_config->password_change_invalidate_other_sessions === 'Y')
if ($config->password_change_invalidate_other_sessions === 'Y')
{
Rhymix\Framework\Session::destroyOtherSessions($member_srl);
}
$this->add('member_srl', $args->member_srl);
$this->setMessage('success_updated');
$this->add('member_srl', $member_srl);
$this->setMessage('member.msg_password_changed');
$returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'mid', Context::get('mid'), 'act', 'dispMemberInfo');
if (Context::get('success_return_url'))
{
$returnUrl = Context::get('success_return_url');
}
else
{
$returnUrl = getNotEncodedUrl('', 'mid', Context::get('mid'), 'act', 'dispMemberInfo');
}
$this->setRedirectUrl($returnUrl);
}
/**
* Change password using auth_key instead of current password
*/
public function procMemberResetPassword()
{
$config = MemberModel::getMemberConfig();
$vars = Context::getRequestVars();
if ($this->user->member_srl)
{
throw new Rhymix\Framework\Exception('already_logged');
}
// Check auth_key
if (empty($vars->auth_key))
{
throw new Rhymix\Framework\Exceptions\InvalidRequest;
}
$output = executeQuery('member.getAuthMail', ['auth_key' => $vars->auth_key]);
if(!$output->toBool() || $output->data->auth_key !== $vars->auth_key)
{
executeQuery('member.deleteAuthMail', ['auth_key' => $vars->auth_key]);
throw new Rhymix\Framework\Exception('msg_invalid_auth_key');
}
$member_srl = $output->data->member_srl;
if (!$member_srl || $output->data->auth_type !== 'password_v2')
{
executeQuery('member.deleteAuthMail', ['auth_key' => $vars->auth_key]);
throw new Rhymix\Framework\Exception('msg_invalid_auth_key');
}
$expires = (intval($config->authmail_expires) * intval($config->authmail_expires_unit)) ?: 86400;
if(ztime($output->data->regdate) < time() - $expires)
{
executeQuery('member.deleteAuthMail', ['auth_key' => $vars->auth_key]);
throw new Rhymix\Framework\Exception('msg_expired_auth_key');
}
// Update the password
$args = new stdClass;
$args->member_srl = $member_srl;
$args->password = trim($vars->password1);
$output = $this->updateMemberPassword($args);
if (!$output->toBool())
{
return $output;
}
// Log out all other sessions.
if ($config->password_change_invalidate_other_sessions === 'Y')
{
Rhymix\Framework\Session::destroyOtherSessions($member_srl);
}
$this->add('member_srl', $member_srl);
$this->setMessage('member.msg_password_changed');
if (Context::get('success_return_url'))
{
$returnUrl = Context::get('success_return_url');
}
else
{
$returnUrl = getNotEncodedUrl('', 'mid', Context::get('mid'), 'act', 'dispMemberLoginForm');
}
$this->setRedirectUrl($returnUrl);
}
@ -1664,22 +1740,32 @@ class MemberController extends Member
if($output->toBool() && $output->data->count != '0') throw new Rhymix\Framework\Exception('msg_user_not_confirmed');
}
// Get password reset method
$member_config = ModuleModel::getModuleConfig('member');
$password_reset_method = intval($member_config->password_reset_method ?? 1);
// Insert data into the authentication DB
$args = new stdClass();
$args->user_id = $member_info->user_id;
$args->member_srl = $member_info->member_srl;
$args->new_password = Rhymix\Framework\Password::getRandomPassword(8);
$args->new_password = $password_reset_method == 2 ? '' : Rhymix\Framework\Password::getRandomPassword(8);
$args->auth_key = Rhymix\Framework\Security::getRandom(40, 'hex');
$args->auth_type = 'password_v' . $password_reset_method;
$args->is_register = 'N';
$output = executeQuery('member.insertAuthMail', $args);
if(!$output->toBool()) return $output;
// Get content of the email to send a member
global $lang;
if ($password_reset_method == 2)
{
$args->new_password = lang('member.msg_change_after_click');
$lang->set('msg_find_account_comment', lang('member.msg_find_account_comment_v2'));
}
Context::set('auth_args', $args);
$member_config = ModuleModel::getModuleConfig('member');
$memberInfo = array();
global $lang;
if(is_array($member_config->signupForm))
{
$exceptForm=array('password', 'find_account_question');
@ -1774,40 +1860,63 @@ class MemberController extends Member
}
// Test logs for finding password by user_id and authkey
$args = new stdClass;
$args->member_srl = $member_srl;
$args->auth_key = $auth_key;
$output = executeQuery('member.getAuthMail', $args);
if(!$output->toBool() || $output->data->auth_key !== $auth_key)
$output = executeQuery('member.getAuthMail', ['auth_key' => $auth_key]);
if(!$output->data || $output->data->auth_key !== $auth_key || $output->data->member_srl != $member_srl)
{
executeQuery('member.deleteAuthMail', $args);
executeQuery('member.deleteAuthMail', ['member_srl' => $member_srl]);
throw new Rhymix\Framework\Exception('msg_invalid_auth_key');
}
$expires = (intval($config->authmail_expires) * intval($config->authmail_expires_unit)) ?: 86400;
if(ztime($output->data->regdate) < time() - $expires)
{
executeQuery('member.deleteAuthMail', $args);
executeQuery('member.deleteAuthMail', ['auth_key' => $auth_key]);
throw new Rhymix\Framework\Exception('msg_expired_auth_key');
}
// Back up the value of $output->data->is_register
$is_register = $output->data->is_register;
$password_reset_method = $output->data->auth_type === 'password_v2' ? 2 : 1;
// If credentials are correct, change the password to a new one
$args = new stdClass;
$args->member_srl = $member_srl;
if($is_register === 'Y')
{
$args->denied = 'N';
$args->status = 'APPROVED';
$query_id = 'member.updateMemberStatus';
$args = [
'member_srl' => $member_srl,
'denied' => 'N',
'status' => 'APPROVED',
];
}
elseif ($password_reset_method == 1)
{
$query_id = 'member.updateMemberPassword';
$args = [
'member_srl' => $member_srl,
'password' => MemberModel::hashPassword($output->data->new_password),
];
}
else
{
$args->password = MemberModel::hashPassword($output->data->new_password);
$query_id = 'member.updateMemberPassword';
$tpl_path = sprintf('%sskins/%s', $this->module_path, $config->skin ?: 'default');
if(!Rhymix\Framework\Storage::isDirectory($tpl_path))
{
$tpl_path = sprintf('%sskins/%s', $this->module_path, 'default');
}
$tpl_file = sprintf('%s/%s', $tpl_path, 'reset_password.html');
if (!Rhymix\Framework\Storage::exists($tpl_file))
{
$tpl_file = sprintf('%s/%s', $tpl_path, 'reset_password.blade.php');
if (!Rhymix\Framework\Storage::exists($tpl_file))
{
$tpl_path = sprintf('%sskins/%s', $this->module_path, 'default');
}
}
$this->setTemplatePath($tpl_path);
$this->setTemplateFile('reset_password');
Context::set('member_config', $config ?? '');
return;
}
$output = executeQuery($query_id, $args);
@ -1825,11 +1934,11 @@ class MemberController extends Member
executeQuery('member.deleteLoginCountByIp', ['ipaddress' => \RX_CLIENT_IP]);
// Clear member cache
self::clearMemberCache($args->member_srl);
self::clearMemberCache($member_srl);
// Call a trigger (after)
$trigger_obj->is_register = $is_register;
$trigger_output = ModuleHandler::triggerCall('member.procMemberAuthAccount', 'after', $trigger_obj);
ModuleHandler::triggerCall('member.procMemberAuthAccount', 'after', $trigger_obj);
// Notify the result
$message = $is_register === 'Y' ? lang('msg_success_confirmed') : lang('msg_success_authed');
@ -2858,6 +2967,7 @@ class MemberController extends Member
$auth_args->member_srl = $args->member_srl;
$auth_args->new_password = $args->password;
$auth_args->auth_key = Rhymix\Framework\Security::getRandom(40, 'hex');
$args->auth_type = 'signup';
$auth_args->is_register = 'Y';
$output = executeQuery('member.insertAuthMail', $auth_args);
@ -3435,6 +3545,8 @@ class MemberController extends Member
$auth_args->member_srl = $member_info->member_srl;
$auth_args->auth_key = Rhymix\Framework\Security::getRandom(40, 'hex');
$auth_args->new_password = 'XE_change_emaill_address';
$auth_args->auth_type = 'change_email';
$auth_args->is_register = 'N';
$output = executeQuery('member.insertAuthMail', $auth_args);
if(!$output->toBool())
@ -3499,7 +3611,7 @@ class MemberController extends Member
$output = executeQuery('member.updateMemberEmailAddress', $args);
if(!$output->toBool()) return $output;
// Remove all values having the member_srl and new_password equal to 'XE_change_emaill_address' from authentication table
// Remove all values having the member_srl and auth_type = change_email
executeQuery('member.deleteAuthChangeEmailAddress',$args);
self::clearMemberCache($args->member_srl);

View file

@ -41,7 +41,7 @@ class MemberView extends Member
{
$is_valid_referer = false;
}
if (preg_match('!/(login|signup)\b!', $referer_url))
if (preg_match('!/(auth|login|signup)\b!', $referer_url))
{
$is_valid_referer = false;
}

View file

@ -4,6 +4,9 @@
</tables>
<conditions>
<condition operation="equal" column="member_srl" var="member_srl" notnull="notnull" />
<condition operation="equal" column="new_password" default="XE_change_emaill_address" notnull="notnull" pipe="and" />
<group pipe="and">
<condition operation="equal" column="new_password" default="XE_change_emaill_address" />
<condition operation="equal" column="auth_type" default="change_email" pipe="or" />
</group>
</conditions>
</query>

View file

@ -3,6 +3,7 @@
<table name="member_auth_mail" />
</tables>
<conditions>
<condition operation="equal" column="member_srl" var="member_srl" notnull="notnull" />
<condition operation="equal" column="member_srl" var="member_srl" />
<condition operation="equal" column="auth_key" var="auth_key" />
</conditions>
</query>

View file

@ -6,7 +6,7 @@
<column name="*" />
</columns>
<conditions>
<condition operation="equal" column="member_srl" var="member_srl" notnull="notnull" />
<condition operation="equal" column="auth_key" var="auth_key" pipe="and" />
<condition operation="equal" column="member_srl" var="member_srl" />
<condition operation="equal" column="auth_key" var="auth_key" />
</conditions>
</query>

View file

@ -3,10 +3,11 @@
<table name="member_auth_mail" />
</tables>
<columns>
<column name="auth_key" var="auth_key" notnull="notnull" minlength="1" maxlength="60" />
<column name="member_srl" var="member_srl" filter="number" notnull="notnull" />
<column name="user_id" var="user_id" notnull="notnull" />
<column name="auth_key" var="auth_key" notnull="notnull" minlength="1" maxlength="60" />
<column name="new_password" var="new_password" notnull="notnull" />
<column name="auth_type" var="auth_type" default="password_v1" />
<column name="is_register" var="is_register" default="N" />
<column name="regdate" default="curdate()" />
</columns>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<ruleset version="1.5.0">
<customrules>
</customrules>
<fields>
<field name="auth_key" required="true" length="1:60" />
<field name="password1" required="true" length="4:60" />
<field name="password2" required="true" length="4:60" equalto="password1" />
</fields>
</ruleset>

View file

@ -1,8 +1,9 @@
<table name="member_auth_mail">
<column name="auth_key" type="varchar" size="60" notnull="notnull" unique="unique_key" />
<column name="member_srl" type="number" size="11" notnull="notnull" unique="unique_key" />
<column name="auth_key" type="varchar" size="60" notnull="notnull" unique="unique_auth_key" />
<column name="member_srl" type="number" size="11" notnull="notnull" index="idx_member_srl" />
<column name="user_id" type="varchar" size="80" notnull="notnull" />
<column name="new_password" type="varchar" size="250" notnull="notnull" />
<column name="is_register" type="char" size="1" default="N" />
<column name="regdate" type="date" index="idx_regdate" />
<column name="auth_type" type="varchar" size="20" notnull="notnull" default="password_v1" />
<column name="is_register" type="char" size="1" notnull="notnull" default="N" />
<column name="regdate" type="date" notnull="notnull" index="idx_regdate" />
</table>

View file

@ -0,0 +1,26 @@
<config autoescape="on" />
<include target="common_header.html" />
<h1>{$member_title = $lang->cmd_modify_member_password}</h1>
<div cond="$XE_VALIDATOR_MESSAGE" class="message {$XE_VALIDATOR_MESSAGE_TYPE}">
<p>{$XE_VALIDATOR_MESSAGE}</p>
</div>
<form action="./" method="post" class="rx_ajax">
<input type="hidden" name="module" value="member" />
<input type="hidden" name="act" value="procMemberResetPassword" />
<input type="hidden" name="mid" value="{$member_config->mid ?? ''}" />
<input type="hidden" name="xe_validator_id" value="modules/member/skins" />
<input type="hidden" name="auth_key" value="{$auth_key}" />
<div>
<input type="password" name="password1" id="npw1" required placeholder="{$lang->password1}" title="{$lang->password1}" />
<span class="help-inline">{$lang->about_password_strength[$member_config->password_strength]}</span>
</div>
<div>
<input type="password" name="password2" id="npw2" required placeholder="{$lang->password2}" title="{$lang->password2}" />
</div>
<div>
<button type="submit" class="btn">{$lang->cmd_modify_member_password}</button>
</div>
</form>
<include target="common_footer.html" />

View file

@ -138,6 +138,14 @@
<p class="x_help-block">{$lang->about_password_change_invalidate_other_sessions}</p>
</div>
</div>
<div class="x_control-group">
<label class="x_control-label">{$lang->cmd_password_reset_method}</label>
<div class="x_controls">
<label for="password_reset_method_v2" class="x_inline"><input type="radio" name="password_reset_method" id="password_reset_method_v2" value="2" checked="checked"|cond="$config->password_reset_method == 2" /> {$lang->cmd_password_reset_method_v2}</label>
<label for="password_reset_method_v1" class="x_inline"><input type="radio" name="password_reset_method" id="password_reset_method_v1" value="1" checked="checked"|cond="$config->password_reset_method != 2" /> {$lang->cmd_password_reset_method_v1}</label>
<p class="x_help-block">{$lang->about_password_reset_method}</p>
</div>
</div>
<div class="x_control-group">
<label class="x_control-label" for="member_sync">{$lang->cmd_member_sync}</label>
<div class="x_controls">