Copybara bot | be50d49 | 2023-11-30 00:16:42 +0100 | [diff] [blame] | 1 | <?php |
| 2 | class recovery { |
| 3 | const TOKEN_BYTES = 32; |
| 4 | const EXPIRATION_TIME = 60*60*24; |
| 5 | const WELCOME_EXPIRATION_TIME = 60*60*24*90; |
| 6 | |
| 7 | const EMAIL_TYPE_RECOVERY = 0; |
| 8 | const EMAIL_TYPE_WELCOME = 1; |
| 9 | |
| 10 | const EMAIL_NOT_SET = 0; |
| 11 | |
| 12 | public static function getUser($email, $dni) { |
| 13 | global $con; |
| 14 | |
| 15 | $semail = db::sanitize($email); |
| 16 | $sdni = db::sanitize($dni); |
| 17 | |
| 18 | $query = mysqli_query($con, "SELECT id FROM people WHERE email = '$semail' AND dni = '$sdni' LIMIT 1"); |
| 19 | |
| 20 | if (!mysqli_num_rows($query)) return false; |
| 21 | |
| 22 | return mysqli_fetch_assoc($query)["id"]; |
| 23 | } |
| 24 | |
| 25 | private static function tokenIsUnique($token) { |
| 26 | global $con; |
| 27 | |
| 28 | $stoken = db::sanitize($token); |
| 29 | |
| 30 | $query = mysqli_query($con, "SELECT 1 FROM recovery WHERE token = '$stoken' LIMIT 1"); |
| 31 | |
| 32 | return !mysqli_num_rows($query); |
| 33 | } |
| 34 | |
| 35 | private static function generateRandomToken() { |
| 36 | do { |
| 37 | $token = bin2hex(random_bytes(self::TOKEN_BYTES)); |
| 38 | } while (!self::tokenIsUnique($token)); |
| 39 | |
| 40 | return $token; |
| 41 | } |
| 42 | |
| 43 | private static function invalidateAllRecoveries($id) { |
| 44 | global $con; |
| 45 | |
| 46 | $sid = (int)$id; |
| 47 | |
| 48 | return mysqli_query($con, "UPDATE recovery SET used = 1 WHERE user = $sid"); |
| 49 | } |
| 50 | |
| 51 | public static function sendRecoveryMail($user, $token, $expires, $emailType = self::EMAIL_TYPE_RECOVERY) { |
| 52 | global $conf; |
| 53 | |
| 54 | $to = [ |
| 55 | ["email" => $user["email"]] |
| 56 | ]; |
| 57 | |
| 58 | $url = security::htmlsafe($conf["fullPath"]."recovery.php?token=".$token); |
| 59 | |
| 60 | switch ($emailType) { |
| 61 | case self::EMAIL_TYPE_WELCOME: |
| 62 | $subject = "Datos de acceso de ".security::htmlsafe($user["name"]); |
| 63 | $body = mail::bodyTemplate("<p>Hola ".security::htmlsafe($user["name"]).",</p> |
| 64 | <p>Para poder acceder al aplicativo de control horario, adjuntamos un enlace donde podrás configurar tu contraseña:</p> |
| 65 | <ul> |
| 66 | <li><a href=\"$url\">$url</a></li> |
| 67 | </ul> |
| 68 | <p>Una vez establezcas tu contraseña, podrás iniciar sesión con la contraseña que has rellenado y tu usuario: <b><code>".security::htmlsafe($user["username"])."</code></b></p> |
| 69 | <p>Reciba un cordial saludo.</p>"); |
| 70 | break; |
| 71 | |
| 72 | default: |
| 73 | $subject = "Recuperar contraseña de ".security::htmlsafe($user["name"]); |
| 74 | $body = mail::bodyTemplate("<p>Hola ".security::htmlsafe($user["name"]).",</p> |
| 75 | <p>Alguien (seguramente tú) ha rellenado el formulario de recuperación de contraseñas para el aplicativo de registro horario.</p> |
| 76 | <p>Si no has sido tú, puede que alguien esté intentando entrar en tu zona de trabajador del aplicativo, y te agredeceríamos que lo comunicaras en la mayor brevedad posible a recursos humanos.</p> |
| 77 | <p>Si has sido tú, puedes restablecer tu contraseña haciendo clic en el siguiente enlace:</p> |
| 78 | <ul> |
| 79 | <li><a href=\"$url\">$url</a></li> |
| 80 | </ul> |
| 81 | <p>Además, aprovechamos para recordarte que tu usuario en el web es <b><code>".security::htmlsafe($user["username"])."</code></b></p> |
| 82 | <p>Reciba un cordial saludo.</p>"); |
| 83 | } |
| 84 | |
| 85 | return mail::send($to, [], $subject, $body); |
| 86 | } |
| 87 | |
| 88 | public static function recover($id, $emailType = self::EMAIL_TYPE_RECOVERY) { |
| 89 | global $con; |
| 90 | |
| 91 | $user = people::get($id); |
| 92 | if ($user === false) return false; |
| 93 | if (!isset($user["email"]) || empty($user["email"])) return self::EMAIL_NOT_SET; |
| 94 | |
| 95 | $token = self::generateRandomToken(); |
| 96 | $stoken = db::sanitize($token); |
| 97 | $sid = (int)$id; |
| 98 | $stime = (int)time(); |
| 99 | $sexpires = (int)($stime + ($emailType === self::EMAIL_TYPE_WELCOME ? self::WELCOME_EXPIRATION_TIME : self::EXPIRATION_TIME)); |
| 100 | |
| 101 | if (!self::invalidateAllRecoveries($id)) return false; |
| 102 | |
| 103 | if (!mysqli_query($con, "INSERT INTO recovery (user, token, timecreated, expires) VALUES ($sid, '$stoken', $stime, $sexpires)")) return false; |
| 104 | |
| 105 | return self::sendRecoveryMail($user, $token, $sexpires, $emailType); |
| 106 | } |
| 107 | |
| 108 | public static function getRecovery($token) { |
| 109 | global $con; |
| 110 | |
| 111 | $stoken = db::sanitize($token); |
| 112 | |
| 113 | $query = mysqli_query($con, "SELECT * FROM recovery WHERE token = '$stoken' LIMIT 1"); |
| 114 | |
| 115 | if (!mysqli_num_rows($query)) return false; |
| 116 | |
| 117 | return mysqli_fetch_assoc($query); |
| 118 | } |
| 119 | |
| 120 | public static function getUnusedRecovery($token) { |
| 121 | $recovery = self::getRecovery($token); |
| 122 | if ($recovery === false || $recovery["used"] != 0 || $recovery["expires"] < time()) return false; |
| 123 | |
| 124 | return $recovery; |
| 125 | } |
| 126 | |
| 127 | public static function finishRecovery($token, $password) { |
| 128 | global $con; |
| 129 | |
| 130 | $stoken = db::sanitize($token); |
| 131 | $spassword = db::sanitize(password_hash($password, PASSWORD_DEFAULT)); |
| 132 | |
| 133 | $recovery = recovery::getUnusedRecovery($token); |
| 134 | if ($recovery === false) return false; |
| 135 | |
| 136 | if (!self::invalidateAllRecoveries($recovery["user"])) return false; |
| 137 | |
| 138 | return people::updatePassword($recovery["user"], $spassword); |
| 139 | } |
| 140 | } |