misuzu/public-legacy/auth/password.php
2025-03-31 15:35:24 +00:00

141 lines
5.1 KiB
PHP

<?php
namespace Misuzu;
use RuntimeException;
use Misuzu\Users\{User,UserPasswordsData};
if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
die('Script must be called through the Misuzu route dispatcher.');
if($msz->authInfo->loggedIn) {
Tools::redirect($msz->urls->format('settings-account'));
return;
}
$userId = !empty($_POST['user']) && is_scalar($_POST['user'])
? (int)$_POST['user'] : (
!empty($_GET['user']) && is_scalar($_GET['user']) ? (int)$_GET['user'] : 0
);
if($userId > 0)
try {
$userInfo = $msz->usersCtx->users->getUser((string)$userId, 'id');
} catch(RuntimeException $ex) {
Tools::redirect($msz->urls->format('auth-forgot'));
return;
}
$notices = [];
$ipAddress = $_SERVER['REMOTE_ADDR'];
$siteIsPrivate = $msz->config->getBoolean('private.enable');
$canResetPassword = $siteIsPrivate ? $msz->config->getBoolean('private.allow_password_reset', true) : true;
$remainingAttempts = $msz->authCtx->loginAttempts->countRemainingAttempts($ipAddress);
while($canResetPassword) {
if(!empty($_POST['verification']) && is_scalar($_POST['verification']) && !empty($userInfo)) {
if(!$msz->csrfCtx->verifyLegacy()) {
$notices[] = 'Was unable to verify the request, please try again!';
break;
}
$verifyCode = (string)$_POST['verification'];
try {
$tokenInfo = $msz->authCtx->recoveryTokens->getToken(verifyCode: $verifyCode);
} catch(RuntimeException $ex) {
unset($tokenInfo);
}
if(empty($tokenInfo) || !$tokenInfo->isValid || $tokenInfo->userId !== (string)$userInfo->id) {
$notices[] = 'Invalid verification code!';
break;
}
$passwordNew = !empty($_POST['password_new']) && is_scalar($_POST['password_new']) ? $_POST['password_new'] : '';
$passwordConfirm = !empty($_POST['password_confirm']) && is_scalar($_POST['password_confirm']) ? $_POST['password_confirm'] : '';
if(empty($passwordNew) || empty($passwordConfirm)
|| $passwordNew !== $passwordConfirm) {
$notices[] = "Password confirmation failed!";
break;
}
$passwordValidation = UserPasswordsData::validateUserPassword($passwordNew);
if($passwordValidation !== '') {
$notices[] = UserPasswordsData::validateUserPasswordText($passwordValidation);
break;
}
// also disables two factor auth to prevent getting locked out of account entirely
// this behaviour should really be replaced with recovery keys...
$msz->usersCtx->passwords->updateUserPassword($userInfo, $passwordNew);
$msz->usersCtx->totps->deleteUserTotp($userInfo);
$msz->logsCtx->createUserLog($userInfo, 'PASSWORD_RESET');
$msz->authCtx->recoveryTokens->invalidateToken($tokenInfo);
Tools::redirect($msz->urls->format('auth-login', ['redirect' => '/']));
return;
}
if(!empty($_POST['email']) && is_scalar($_POST['email'])) {
if(!$msz->csrfCtx->verifyLegacy()) {
$notices[] = 'Was unable to verify the request, please try again!';
break;
}
if($remainingAttempts < 1) {
$notices[] = "There are too many failed login attempts from your IP address, please try again later.";
break;
}
try {
$forgotUser = $msz->usersCtx->users->getUser((string)$_POST['email'], 'email');
} catch(RuntimeException $ex) {
unset($forgotUser);
}
if(empty($forgotUser) || $forgotUser->deleted) {
$notices[] = "This e-mail address is not registered with us.";
break;
}
try {
$tokenInfo = $msz->authCtx->recoveryTokens->getToken(userInfo: $forgotUser, remoteAddr: $ipAddress);
} catch(RuntimeException $ex) {
$tokenInfo = $msz->authCtx->recoveryTokens->createToken($forgotUser, $ipAddress);
$msz->initMailer();
$recoveryMessage = Mailer::template('password-recovery', [
'username' => $forgotUser->name,
'token' => $tokenInfo->code,
]);
$recoveryMail = Mailer::sendMessage(
[$forgotUser->emailAddress => $forgotUser->name],
$recoveryMessage['subject'], $recoveryMessage['message']
);
if(!$recoveryMail) {
$notices[] = "Failed to send reset email, please contact the administrator.";
$msz->authCtx->recoveryTokens->invalidateToken($tokenInfo);
break;
}
}
Tools::redirect($msz->urls->format('auth-reset', ['user' => $forgotUser->id]));
return;
}
break;
}
Template::render(isset($userInfo) ? 'auth.password_reset' : 'auth.password_forgot', [
'password_notices' => $notices,
'password_email' => !empty($_POST['email']) && is_scalar($_POST['email']) ? (string)$_POST['email'] : '',
'password_attempts_remaining' => $remainingAttempts,
'password_user' => $userInfo ?? null,
'password_verification' => $verifyCode ?? '',
]);