db = $db; } // ------------------------------------------------------------------------- // Odczyt // ------------------------------------------------------------------------- public function find( int $userId ): ?array { return $this->db->get( 'pp_users', '*', [ 'id' => $userId ] ) ?: null; } public function findByLogin( string $login ): ?array { return $this->db->get( 'pp_users', '*', [ 'login' => $login ] ) ?: null; } public function all(): array { return $this->db->select( 'pp_users', '*' ) ?: []; } public function privileges( int $userId ): array { return $this->db->select( 'pp_users_privileges', '*', [ 'id_user' => $userId ] ) ?: []; } public function hasPrivilege( string $name, int $userId ): bool { if ( $userId === 1 ) return true; if ( !$result = \Shared\Cache\CacheHandler::fetch( "check_privileges:$userId:$name-tmp" ) ) { $result = $this->db->count( 'pp_users_privileges', [ 'AND' => [ 'name' => $name, 'id_user' => $userId ] ] ); \Shared\Cache\CacheHandler::store( "check_privileges:$userId:$name", $result ); } return (bool) $result; } // ------------------------------------------------------------------------- // Logowanie // ------------------------------------------------------------------------- /** * Weryfikuje login i hasło. * @return int 1 = OK, 0 = złe dane, -1 = konto zablokowane */ public function logon( string $login, string $password ): int { if ( !$this->db->get( 'pp_users', '*', [ 'login' => $login ] ) ) return 0; if ( !$this->db->get( 'pp_users', '*', [ 'AND' => [ 'login' => $login, 'status' => 1, 'error_logged_count[<]' => 5 ] ] ) ) return -1; if ( $this->db->get( 'pp_users', '*', [ 'AND' => [ 'login' => $login, 'status' => 1, 'password' => md5( $password ), 'OR' => [ 'active_to[>=]' => date( 'Y-m-d' ), 'active_to' => null ] ] ] ) ) { $this->db->update( 'pp_users', [ 'last_logged' => date( 'Y-m-d H:i:s' ), 'error_logged_count' => 0 ], [ 'login' => $login ] ); return 1; } $this->db->update( 'pp_users', [ 'last_error_logged' => date( 'Y-m-d H:i:s' ), 'error_logged_count[+]' => 1 ], [ 'login' => $login ] ); if ( $this->db->get( 'pp_users', 'error_logged_count', [ 'login' => $login ] ) >= 5 ) { $this->db->update( 'pp_users', [ 'status' => 0 ], [ 'login' => $login ] ); return -1; } return 0; } public function isLoginTaken( string $login, int $excludeId = 0 ): bool { return (bool) $this->db->get( 'pp_users', 'login', [ 'AND' => [ 'login' => $login, 'id[!]' => $excludeId ] ] ); } // ------------------------------------------------------------------------- // 2FA // ------------------------------------------------------------------------- public function update( int $userId, array $data ): bool { return (bool) $this->db->update( 'pp_users', $data, [ 'id' => $userId ] ); } public function sendTwofaCode( int $userId, bool $resend = false ): bool { $user = $this->find( $userId ); if ( !$user ) return false; if ( (int)$user['twofa_enabled'] !== 1 ) return false; $to = $user['twofa_email'] ?: $user['login']; if ( !filter_var( $to, FILTER_VALIDATE_EMAIL ) ) return false; if ( $resend && !empty( $user['twofa_sent_at'] ) ) { $last = strtotime( $user['twofa_sent_at'] ); if ( $last && ( time() - $last ) < 30 ) return false; } $code = random_int( 100000, 999999 ); $hash = password_hash( (string)$code, PASSWORD_DEFAULT ); $this->update( $userId, [ 'twofa_code_hash' => $hash, 'twofa_expires_at' => date( 'Y-m-d H:i:s', time() + 10 * 60 ), 'twofa_sent_at' => date( 'Y-m-d H:i:s' ), 'twofa_failed_attempts' => 0, ] ); $subject = 'Twój kod logowania 2FA'; $body = "Twój kod logowania do panelu administratora: {$code}. Kod jest ważny przez 10 minut. Jeśli to nie Ty inicjowałeś logowanie – zignoruj tę wiadomość i poinformuj administratora."; $sent = \S::send_email( $to, $subject, $body ); if ( !$sent ) { $headers = "MIME-Version: 1.0\r\n"; $headers .= "Content-type: text/plain; charset=UTF-8\r\n"; $headers .= "From: no-reply@" . ( $_SERVER['HTTP_HOST'] ?? 'localhost' ) . "\r\n"; $sent = mail( $to, mb_encode_mimeheader( $subject, 'UTF-8' ), $body, $headers ); } return (bool) $sent; } public function verifyTwofaCode( int $userId, string $code ): bool { $user = $this->find( $userId ); if ( !$user ) return false; if ( (int)$user['twofa_failed_attempts'] >= 5 ) return false; if ( empty( $user['twofa_expires_at'] ) || time() > strtotime( $user['twofa_expires_at'] ) ) { $this->update( $userId, [ 'twofa_code_hash' => null, 'twofa_expires_at' => null ] ); return false; } $ok = !empty( $user['twofa_code_hash'] ) && password_verify( $code, $user['twofa_code_hash'] ); if ( $ok ) { $this->update( $userId, [ 'twofa_code_hash' => null, 'twofa_expires_at' => null, 'twofa_sent_at' => null, 'twofa_failed_attempts' => 0, 'last_logged' => date( 'Y-m-d H:i:s' ), ] ); return true; } $this->update( $userId, [ 'twofa_failed_attempts' => (int)$user['twofa_failed_attempts'] + 1, 'last_error_logged' => date( 'Y-m-d H:i:s' ), ] ); return false; } // ------------------------------------------------------------------------- // Zapis / usuwanie // ------------------------------------------------------------------------- public function save( $userId, string $login, $status, $activeTo, string $password, string $passwordRe, $admin, $privileges, $twofaEnabled = 0, string $twofaEmail = '' ): array { $this->db->delete( 'pp_users_privileges', [ 'id_user' => (int)$userId ] ); if ( !$userId ) { if ( strlen( $password ) < 5 ) return [ 'status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.' ]; if ( $password !== $passwordRe ) return [ 'status' => 'error', 'msg' => 'Podane hasła są różne.' ]; $this->db->insert( 'pp_users', [ 'login' => $login, 'status' => $status == 'on' ? 1 : 0, 'active_to' => $activeTo === '' ? null : $activeTo, 'admin' => $admin, 'password' => md5( $password ), 'twofa_enabled' => $twofaEnabled == 'on' ? 1 : 0, 'twofa_email' => $twofaEmail, ] ); $userId = $this->db->get( 'pp_users', 'id', [ 'ORDER' => [ 'id' => 'DESC' ] ] ); } else { if ( $password && strlen( $password ) < 5 ) return [ 'status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.' ]; if ( $password && $password !== $passwordRe ) return [ 'status' => 'error', 'msg' => 'Podane hasła są różne.' ]; if ( $password ) $this->db->update( 'pp_users', [ 'password' => md5( $password ) ], [ 'id' => (int)$userId ] ); $this->db->update( 'pp_users', [ 'login' => $login, 'admin' => $admin, 'status' => $status == 'on' ? 1 : 0, 'active_to' => $activeTo === '' ? null : $activeTo, 'error_logged_count' => 0, 'twofa_enabled' => $twofaEnabled == 'on' ? 1 : 0, 'twofa_email' => $twofaEmail, ], [ 'id' => (int)$userId ] ); } $privileges = (array)$privileges; foreach ( $privileges as $pri ) $this->db->insert( 'pp_users_privileges', [ 'name' => $pri, 'id_user' => $userId ] ); \S::delete_cache(); return [ 'status' => 'ok', 'msg' => 'Użytkownik został zapisany.' ]; } public function delete( int $userId ): bool { return (bool) $this->db->delete( 'pp_users', [ 'id' => $userId ] ); } }