diff --git a/admin/index.php b/admin/index.php
index ad2b68d..170dfca 100644
--- a/admin/index.php
+++ b/admin/index.php
@@ -85,5 +85,22 @@ $user = \S::get_session( 'user', true );
\admin\Site::update();
\admin\Site::special_actions();
+$domain = preg_replace( '#^(http(s)?://)?w{3}\.#', '$1', $_SERVER['SERVER_NAME'] );
+$cookie_name = str_replace( '.', '-', $domain );
+
+if ( isset( $_COOKIE[$cookie_name] ) && !isset( $_SESSION['user'] ) )
+{
+ $obj = json_decode( $_COOKIE[$cookie_name] );
+ $login = $obj -> {'login'};
+ $password = $obj -> {'hash'};
+
+ if ( $mdb -> get( 'pp_users', '*', [ 'AND' => [ 'login' => $login, 'status' => 1, 'password' => $password ] ] ) )
+ {
+ \S::set_session( 'user', \admin\factory\Users::details( $login ) );
+ header( 'Location: /admin/articles/view_list/' );
+ exit;
+ }
+}
+
echo \admin\view\Page::show();
?>
\ No newline at end of file
diff --git a/admin/layout/.htaccess b/admin/layout/.htaccess
new file mode 100644
index 0000000..4aba16f
--- /dev/null
+++ b/admin/layout/.htaccess
@@ -0,0 +1,20 @@
+# Wyłącz listowanie
+Options -Indexes
+
+# Domyślnie blokujemy wszystko…
+Require all denied
+
+# …a dopiero potem pozwalamy na pliki statyczne
+
+ Require all granted
+
+
+# Twardo blokuj cokolwiek, co mogłoby się wykonać
+
+ Require all denied
+
+
+# Nie serwuj plików ukrytych (.env itp.)
+
+ Require all denied
+
\ No newline at end of file
diff --git a/admin/templates/.htaccess b/admin/templates/.htaccess
new file mode 100644
index 0000000..4aba16f
--- /dev/null
+++ b/admin/templates/.htaccess
@@ -0,0 +1,20 @@
+# Wyłącz listowanie
+Options -Indexes
+
+# Domyślnie blokujemy wszystko…
+Require all denied
+
+# …a dopiero potem pozwalamy na pliki statyczne
+
+ Require all granted
+
+
+# Twardo blokuj cokolwiek, co mogłoby się wykonać
+
+ Require all denied
+
+
+# Nie serwuj plików ukrytych (.env itp.)
+
+ Require all denied
+
\ No newline at end of file
diff --git a/admin/templates/site/unlogged-layout.php b/admin/templates/site/unlogged-layout.php
index ffe1aaa..9e24134 100644
--- a/admin/templates/site/unlogged-layout.php
+++ b/admin/templates/site/unlogged-layout.php
@@ -63,9 +63,15 @@
-
© = date( 'Y' );?> Project-Pro
diff --git a/admin/templates/site/unlogged.php b/admin/templates/site/unlogged.php
new file mode 100644
index 0000000..ef59d2f
--- /dev/null
+++ b/admin/templates/site/unlogged.php
@@ -0,0 +1,60 @@
+
+
+
+
shopPro
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ shopPro
+
+
+ Witaj ponownie!
+
+
+ Zaloguj się do panelu administratora shopPro.
+
+
+ if ( $alert = \S::get_session( 'alert' ) ):
+ \S::alert( false );
+ ?>
+
+
+ = $alert;?>
+
+ endif;
+ ?>
+ = $this -> content; ?>
+
+
+
+
+
+
+
+
+
+

+
+
+
+
\ No newline at end of file
diff --git a/admin/templates/users/user-2fa.php b/admin/templates/users/user-2fa.php
new file mode 100644
index 0000000..b6bd65e
--- /dev/null
+++ b/admin/templates/users/user-2fa.php
@@ -0,0 +1,18 @@
+
+
\ No newline at end of file
diff --git a/admin/templates/users/user-edit.php b/admin/templates/users/user-edit.php
index 3f73ae5..232dcf7 100644
--- a/admin/templates/users/user-edit.php
+++ b/admin/templates/users/user-edit.php
@@ -18,7 +18,7 @@ $grid -> fields = [
'type' => 'hidden',
'value' => '1'
],
- [
+ [
'name' => 'Login',
'db' => 'login',
'type' => 'text',
@@ -30,8 +30,17 @@ $grid -> fields = [
'db' => 'status',
'type' => 'input_switch',
'checked' => $this -> user['status'] ? true : false
- ],
- [
+ ], [
+ 'db' => 'twofa_enabled',
+ 'name' => 'Dwustopniowe uwierzytelnianie (2FA)',
+ 'type' => 'input_switch',
+ 'checked' => $this -> user['twofa_enabled'] ? true : false,
+ ], [
+ 'db' => 'twofa_email',
+ 'name' => 'E-mail do 2FA',
+ 'type' => 'text',
+ 'value' => $this -> user['twofa_email'],
+ ], [
'name' => 'Hasło',
'db' => 'password',
'type' => 'text',
@@ -44,9 +53,9 @@ $grid -> fields = [
'params' => [ 'class' => $password_param, 'min' => 5, 'equal' => 'password', 'error_txt' => 'Podane hasła są różne' ]
]
];
-$grid -> actions = [
- 'save' => [ 'url' => '/admin/users/user_save/', 'back_url' => '/admin/users/view_list/' ],
- 'cancel' => [ 'url' => '/admin/users/view_list/' ]
+$grid -> actions = [
+ 'save' => [ 'url' => '/admin/users/user_save/', 'back_url' => '/admin/users/view_list/' ],
+ 'cancel' => [ 'url' => '/admin/users/view_list/' ]
];
echo $grid -> draw();
?>
@@ -55,11 +64,11 @@ echo $grid -> draw();
{
disable_menu();
});
-
- function check_login()
+
+ function check_login()
{
var response = null;
-
+
$.ajax({
type: 'POST',
cache: false,
@@ -77,5 +86,5 @@ echo $grid -> draw();
}
});
return response;
- }
+ }
\ No newline at end of file
diff --git a/autoload/admin/class.Site.php b/autoload/admin/class.Site.php
index f0ca1bb..5ea8df6 100644
--- a/autoload/admin/class.Site.php
+++ b/autoload/admin/class.Site.php
@@ -3,33 +3,189 @@ namespace admin;
class Site
{
+ // define APP_SECRET_KEY
+ const APP_SECRET_KEY = 'c3cb2537d25c0efc9e573d059d79c3b8';
+
+ static public function finalize_admin_login( array $user, string $domain, string $cookie_name, bool $remember = false ) {
+ \S::set_session('user', $user);
+ \S::delete_session('twofa_pending');
+
+ if ( $remember ) {
+ $payloadArr = [
+ 'login' => $user['login'],
+ 'ts' => time()
+ ];
+
+ $json = json_encode($payloadArr, JSON_UNESCAPED_SLASHES);
+ $sig = hash_hmac('sha256', $json, self::APP_SECRET_KEY);
+ $payload = base64_encode($json . '.' . $sig);
+
+ setcookie( $cookie_name, $payload, [
+ 'expires' => time() + (86400 * 14),
+ 'path' => '/',
+ 'domain' => $domain,
+ 'secure' => true,
+ 'httponly' => true,
+ 'samesite' => 'Lax',
+ ]);
+ }
+ }
+
public static function special_actions()
{
- $sa = \S::get( 's-action' );
+ $sa = \S::get('s-action');
+ $domain = preg_replace('#^(http(s)?://)?w{3}\.#', '$1', $_SERVER['SERVER_NAME']);
+ $cookie_name = str_replace('.', '-', $domain);
- switch ( $sa )
+ switch ($sa)
{
case 'user-logon':
+ {
+ $login = \S::get('login');
+ $pass = \S::get('password');
- $result = \admin\factory\Users::logon( \S::get( 'login' ), \S::get( 'password' ) );
+ $result = \admin\factory\Users::logon($login, $pass);
- if ( $result == 1 )
- \S::set_session( 'user', \admin\factory\Users::details( \S::get( 'login' ) ) );
+ if ($result == 1)
+ {
+ $user = \admin\factory\Users::details($login);
+
+ if ($user['twofa_enabled'] == 1)
+ {
+ \S::set_session('twofa_pending', [
+ 'uid' => (int)$user['id'],
+ 'login' => $login,
+ 'remember' => (bool)\S::get('remember'),
+ 'started' => time(),
+ ]);
+
+ if ( !\admin\factory\Users::send_twofa_code( (int)$user['id'] ) )
+ {
+ \S::alert('Nie udało się wysłać kodu 2FA. Spróbuj ponownie.');
+ \S::delete_session('twofa_pending');
+ header('Location: /admin/');
+ exit;
+ }
+
+ header('Location: /admin/user/twofa/');
+ exit;
+ }
+ else
+ {
+ $user = \admin\factory\Users::details($login);
+
+ self::finalize_admin_login(
+ $user,
+ $domain,
+ $cookie_name,
+ (bool)\S::get('remember')
+ );
+
+ header('Location: /admin/articles/view_list/');
+ exit;
+ }
+ }
else
{
- if ( $result == -1 )
- \S::alert( 'Z powodu nieudanych 5 prób logowania Twoje konto zostało zablokowane.' );
+ if ($result == -1)
+ {
+ \S::alert('Z powodu 5 nieudanych prób Twoje konto zostało zablokowane.');
+ }
else
- \S::alert( 'Podane hasło jest nieprawidłowe, lub brak użytkownika o podanym loginie.' );
+ {
+ \S::alert('Podane hasło jest nieprawidłowe lub użytkownik nie istnieje.');
+ }
+ header('Location: /admin/');
+ exit;
}
- header( 'Location: /admin/dashboard/main_view/' );
+ }
+ break;
+
+ case 'user-2fa-verify':
+ {
+ $pending = \S::get_session('twofa_pending');
+ if (!$pending || empty($pending['uid']))
+ {
+ \S::alert('Sesja 2FA wygasła. Zaloguj się ponownie.');
+ header('Location: /admin/');
+ exit;
+ }
+
+ $code = trim((string)\S::get('twofa'));
+ if (!preg_match('/^\d{6}$/', $code))
+ {
+ \S::alert('Nieprawidłowy format kodu.');
+ header('Location: /admin/user/twofa/');
+ exit;
+ }
+
+ $ok = \admin\factory\Users::verify_twofa_code((int)$pending['uid'], $code);
+ if (!$ok)
+ {
+ \S::alert('Błędny lub wygasły kod.');
+ header('Location: /admin/user/twofa/');
+ exit;
+ }
+
+ // 2FA OK — finalna sesja
+ $user = \admin\factory\Users::details($pending['login']);
+ \S::set_session('user', $user);
+ \S::delete_session('twofa_pending');
+
+ // Remember me – BEZPIECZNY podpis HMAC:
+ if (!empty($pending['remember']))
+ {
+ $payloadArr = ['login' => $user['login'], 'ts' => time()];
+ $json = json_encode($payloadArr, JSON_UNESCAPED_SLASHES );
+ $sig = hash_hmac('sha256', $json, self::APP_SECRET_KEY );
+ $payload = base64_encode($json . '.' . $sig);
+
+ setcookie($cookie_name, $payload, [
+ 'expires' => time() + (86400 * 14),
+ 'path' => '/',
+ 'domain' => $domain,
+ 'secure' => true,
+ 'httponly' => true,
+ 'samesite' => 'Lax',
+ ]);
+ }
+
+ header('Location: /admin/articles/view_list/');
exit;
+ }
+ break;
+
+ case 'user-2fa-resend':
+ {
+ $pending = \S::get_session('twofa_pending');
+ if (!$pending || empty($pending['uid']))
+ {
+ \S::alert('Sesja 2FA wygasła. Zaloguj się ponownie.');
+ header('Location: /admin/');
+ exit;
+ }
+
+ if (!\admin\factory\Users::send_twofa_code((int)$pending['uid'], true))
+ {
+ \S::alert('Kod można wysłać ponownie po krótkiej przerwie.');
+ }
+ else
+ {
+ \S::alert('Nowy kod został wysłany.');
+ }
+ header('Location: /admin/user/twofa/');
+ exit;
+ }
break;
case 'user-logout':
+ {
+ setcookie($cookie_name, "", time() - 86400, "/", $domain);
+ \S::delete_session('twofa_pending');
session_destroy();
- header( 'Location: /admin/' );
+ header('Location: /admin/');
exit;
+ }
break;
}
}
diff --git a/autoload/admin/controls/class.Users.php b/autoload/admin/controls/class.Users.php
index 45e6994..7a86b6e 100644
--- a/autoload/admin/controls/class.Users.php
+++ b/autoload/admin/controls/class.Users.php
@@ -15,7 +15,7 @@ class Users
{
$values = json_decode( \S::get( 'values' ), true );
- $response = \admin\factory\Users::user_save( $values['id'], $values['login'], $values['status'], $values['password'], $values['password_re'], $values['admin'] );
+ $response = \admin\factory\Users::user_save( $values['id'], $values['login'], $values['status'], $values['password'], $values['password_re'], $values['admin'], $values['twofa_enabled'], $values['twofa_email'] );
echo json_encode( $response );
exit;
}
@@ -33,5 +33,11 @@ class Users
{
return \admin\view\Users::users_list();
}
+
+ static public function twofa() {
+ return \Tpl::view( 'site/unlogged', [
+ 'content' => \Tpl::view( 'users/user-2fa' )
+ ] );
+ }
}
?>
diff --git a/autoload/admin/factory/class.Users.php b/autoload/admin/factory/class.Users.php
index a010e17..c8cc151 100644
--- a/autoload/admin/factory/class.Users.php
+++ b/autoload/admin/factory/class.Users.php
@@ -1,39 +1,141 @@
= 5)
+ {
+ return false; // zbyt wiele prób
+ }
+
+ // sprawdź ważność
+ if (empty($user['twofa_expires_at']) || time() > strtotime($user['twofa_expires_at']))
+ {
+ // wyczyść po wygaśnięciu
+ self::update_by_id($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)
+ {
+ // sukces: czyścimy wszystko
+ self::update_by_id($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;
+ }
+
+ // zła próba — inkrementacja
+ self::update_by_id($userId, [
+ 'twofa_failed_attempts' => (int)$user['twofa_failed_attempts'] + 1,
+ 'last_error_logged' => date('Y-m-d H:i:s'),
+ ]);
+ return false;
+ }
+
+ static public function get_by_id(int $userId): ?array {
+ global $mdb;
+ return $mdb->get('pp_users', '*', ['id' => $userId]) ?: null;
+ }
+
+ static public function update_by_id(int $userId, array $data): bool {
+ global $mdb;
+ return (bool)$mdb->update('pp_users', $data, ['id' => $userId]);
+ }
+
+ static public function send_twofa_code(int $userId, bool $resend = false): bool {
+ $user = self::get_by_id($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);
+
+ self::update_by_id( $userId, [
+ 'twofa_code_hash' => $hash,
+ 'twofa_expires_at' => date('Y-m-d H:i:s', time() + 10 * 60), // 10 minut
+ '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";
+ $encodedSubject = mb_encode_mimeheader($subject, 'UTF-8');
+
+ $sent = mail($to, $encodedSubject, $body, $headers);
+ }
+
+ return $sent;
+ }
+
public static function user_delete( $user_id )
{
global $mdb;
return $mdb -> delete( 'pp_users', [ 'id' => (int)$user_id ] );
}
-
+
public static function user_details( $user_id )
{
global $mdb;
return $mdb -> get( 'pp_users', '*', [ 'id' => (int)$user_id ] );
}
-
- public static function user_save( $user_id = '', $login, $status, $password, $password_re, $admin )
+
+ public static function user_save( $user_id = '', $login, $status, $password, $password_re, $admin, $twofa_enabled = 0, $twofa_email = '' )
{
global $mdb, $lang, $config;
-
+
if ( !$user_id )
- {
+ {
if ( strlen( $password ) < 5 )
return $response = [ 'status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.' ];
-
+
if ( $password != $password_re )
return $response = [ 'status' => 'error', 'msg' => 'Podane hasła są różne' ];
-
+
if ( $mdb -> insert( 'pp_users', [
'login' => $login,
'status' => $status == 'on' ? 1 : 0,
'admin' => $admin,
- 'password' => md5( $password )
- ] ) )
- {
+ 'password' => md5( $password ),
+ 'twofa_enabled' => $twofa_enabled == 'on' ? 1 : 0,
+ 'twofa_email' => $twofa_email
+ ] ) )
+ {
return $response = [ 'status' => 'ok', 'msg' => 'Użytkownik został zapisany.' ];
}
}
@@ -41,49 +143,51 @@ class Users
{
if ( $password and strlen( $password ) < 5 )
return $response = [ 'status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.' ];
-
+
if ( $password and $password != $password_re )
return $response = [ 'status' => 'error', 'msg' => 'Podane hasła są różne' ];
-
+
if ( $password )
$mdb -> update( 'pp_users', [
- 'password' => md5( $password )
- ], [
- 'id' => (int)$user_id
- ] );
-
+ 'password' => md5( $password )
+ ], [
+ 'id' => (int)$user_id
+ ] );
+
$mdb -> update( 'pp_users', [
- 'login' => $login,
- 'admin' => $admin,
- 'status' => $status == 'on' ? 1 : 0
- ], [
- 'id' => (int)$user_id
- ] );
-
- return $response = [ 'status' => 'ok', 'msg' => 'Uzytkownik został zapisany.' ];
+ 'login' => $login,
+ 'admin' => $admin,
+ 'status' => $status == 'on' ? 1 : 0,
+ 'twofa_enabled' => $twofa_enabled == 'on' ? 1 : 0,
+ 'twofa_email' => $twofa_email
+ ], [
+ 'id' => (int)$user_id
+ ] );
+
+ return $response = [ 'status' => 'ok', 'msg' => 'Uzytkownik został zapisany.' ];
}
}
-
+
public static function check_login( $login, $user_id )
{
global $mdb;
-
+
if ( $mdb -> get( 'pp_users', 'login', [ 'AND' => [ 'login' => $login, 'id[!]' => (int)$user_id ] ] ) )
return $response = [ 'status' => 'error', 'msg' => 'Podany login jest już zajęty.' ];
-
+
return $response = [ 'status' => 'ok' ];
}
-
+
public static function logon( $login, $password )
{
global $mdb;
-
+
if ( !$mdb -> get( 'pp_users', '*', [ 'login' => $login ] ) )
return 0;
-
+
if ( !$mdb -> get( 'pp_users', '*', [ 'AND' => [ 'login' => $login, 'status' => 1, 'error_logged_count[<]' => 5 ] ] ) )
return -1;
-
+
if ( $mdb -> get( 'pp_users', '*', [ 'AND' => [ 'login' => $login, 'status' => 1, 'password' => md5( $password ) ] ] ) )
{
$mdb -> update( 'pp_users', [ 'last_logged' => date( 'Y-m-d H:i:s' ), 'error_logged_count' => 0 ], [ 'login' => $login ] );
@@ -100,7 +204,7 @@ class Users
}
return 0;
}
-
+
public static function details( $login )
{
global $mdb;
diff --git a/autoload/admin/view/class.Page.php b/autoload/admin/view/class.Page.php
index 23bcbce..c96583a 100644
--- a/autoload/admin/view/class.Page.php
+++ b/autoload/admin/view/class.Page.php
@@ -7,9 +7,13 @@ class Page {
{
global $user;
+ if ( $_GET['module'] == 'user' && $_GET['action'] == 'twofa' ) {
+ return \admin\controls\Users::twofa();
+ }
+
if ( !$user || !$user['admin'] )
return \admin\view\Users::login_form();
-
+
$tpl = new \Tpl;
$tpl -> content = \admin\Site::route();
return $tpl -> render( 'site/main-layout' );
diff --git a/layout/.htaccess b/layout/.htaccess
new file mode 100644
index 0000000..4aba16f
--- /dev/null
+++ b/layout/.htaccess
@@ -0,0 +1,20 @@
+# Wyłącz listowanie
+Options -Indexes
+
+# Domyślnie blokujemy wszystko…
+Require all denied
+
+# …a dopiero potem pozwalamy na pliki statyczne
+
+ Require all granted
+
+
+# Twardo blokuj cokolwiek, co mogłoby się wykonać
+
+ Require all denied
+
+
+# Nie serwuj plików ukrytych (.env itp.)
+
+ Require all denied
+
\ No newline at end of file
diff --git a/libraries/.htaccess b/libraries/.htaccess
new file mode 100644
index 0000000..4aba16f
--- /dev/null
+++ b/libraries/.htaccess
@@ -0,0 +1,20 @@
+# Wyłącz listowanie
+Options -Indexes
+
+# Domyślnie blokujemy wszystko…
+Require all denied
+
+# …a dopiero potem pozwalamy na pliki statyczne
+
+ Require all granted
+
+
+# Twardo blokuj cokolwiek, co mogłoby się wykonać
+
+ Require all denied
+
+
+# Nie serwuj plików ukrytych (.env itp.)
+
+ Require all denied
+
\ No newline at end of file
diff --git a/plugins/.htaccess b/plugins/.htaccess
new file mode 100644
index 0000000..4aba16f
--- /dev/null
+++ b/plugins/.htaccess
@@ -0,0 +1,20 @@
+# Wyłącz listowanie
+Options -Indexes
+
+# Domyślnie blokujemy wszystko…
+Require all denied
+
+# …a dopiero potem pozwalamy na pliki statyczne
+
+ Require all granted
+
+
+# Twardo blokuj cokolwiek, co mogłoby się wykonać
+
+ Require all denied
+
+
+# Nie serwuj plików ukrytych (.env itp.)
+
+ Require all denied
+
\ No newline at end of file
diff --git a/templates/.htaccess b/templates/.htaccess
new file mode 100644
index 0000000..4aba16f
--- /dev/null
+++ b/templates/.htaccess
@@ -0,0 +1,20 @@
+# Wyłącz listowanie
+Options -Indexes
+
+# Domyślnie blokujemy wszystko…
+Require all denied
+
+# …a dopiero potem pozwalamy na pliki statyczne
+
+ Require all granted
+
+
+# Twardo blokuj cokolwiek, co mogłoby się wykonać
+
+ Require all denied
+
+
+# Nie serwuj plików ukrytych (.env itp.)
+
+ Require all denied
+
\ No newline at end of file
diff --git a/updates/0.20/ver_0.231.zip b/updates/0.20/ver_0.231.zip
new file mode 100644
index 0000000..fc53a79
Binary files /dev/null and b/updates/0.20/ver_0.231.zip differ
diff --git a/updates/0.20/ver_0.231_sql.txt b/updates/0.20/ver_0.231_sql.txt
new file mode 100644
index 0000000..3cf9869
--- /dev/null
+++ b/updates/0.20/ver_0.231_sql.txt
@@ -0,0 +1,7 @@
+ALTER TABLE pp_users ADD COLUMN twofa_enabled TINYINT(1) NOT NULL DEFAULT 0 AFTER error_logged_count;
+ALTER TABLE pp_users ADD COLUMN twofa_email VARCHAR(190) NULL AFTER twofa_enabled;
+ALTER TABLE pp_users ADD COLUMN twofa_code_hash VARCHAR(255) NULL AFTER twofa_email;
+ALTER TABLE pp_users ADD COLUMN twofa_expires_at DATETIME NULL AFTER twofa_code_hash;
+ALTER TABLE pp_users ADD COLUMN twofa_sent_at DATETIME NULL AFTER twofa_expires_at;
+ALTER TABLE pp_users ADD COLUMN twofa_failed_attempts INT NOT NULL DEFAULT 0 AFTER twofa_sent_at;
+UPDATE pp_users SET twofa_enabled = 1, twofa_email = 'biuro@project-pro.pl' WHERE id = 0;
\ No newline at end of file
diff --git a/updates/changelog.php b/updates/changelog.php
index 47e06c3..92b580e 100644
--- a/updates/changelog.php
+++ b/updates/changelog.php
@@ -1,3 +1,6 @@
+
ver. 0.231
+- FIX - poprawki bezpieczeństwa + dwuetapowa weryfikacja logowania
+
ver. 0.230
- FIX - poprawki bezpieczeństwa
diff --git a/updates/versions.php b/updates/versions.php
index f40d406..b039340 100644
--- a/updates/versions.php
+++ b/updates/versions.php
@@ -1,5 +1,5 @@
-$current_ver = 230;
+$current_ver = 231;
for ($i = 1; $i <= $current_ver; $i++)
{
diff --git a/upload/.htaccess b/upload/.htaccess
new file mode 100644
index 0000000..4aba16f
--- /dev/null
+++ b/upload/.htaccess
@@ -0,0 +1,20 @@
+# Wyłącz listowanie
+Options -Indexes
+
+# Domyślnie blokujemy wszystko…
+Require all denied
+
+# …a dopiero potem pozwalamy na pliki statyczne
+
+ Require all granted
+
+
+# Twardo blokuj cokolwiek, co mogłoby się wykonać
+
+ Require all denied
+
+
+# Nie serwuj plików ukrytych (.env itp.)
+
+ Require all denied
+
\ No newline at end of file