From 26f4609b043d394fc6d386d52c31c45c4d39ac80 Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Sun, 8 Sep 2024 14:15:10 -0700 Subject: [PATCH 1/3] Renumber migration 52 => 54 I misremembered the number we checkpointed at. This is rightfully migration 54. --- gatherling/Data/sql/migrations/{52.sql => 54.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename gatherling/Data/sql/migrations/{52.sql => 54.sql} (100%) diff --git a/gatherling/Data/sql/migrations/52.sql b/gatherling/Data/sql/migrations/54.sql similarity index 100% rename from gatherling/Data/sql/migrations/52.sql rename to gatherling/Data/sql/migrations/54.sql From a09b8ed1f23f700f9f4370731890eeb5b57da226 Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Sun, 8 Sep 2024 14:15:52 -0700 Subject: [PATCH 2/3] Don't setup test database on call to upgrade-db and give me a heart attack We want to run this on prod. The DatabaseTestListener does this for tests anyway and nothing else uses the test database. --- gatherling/admin/db-upgrade.php | 1 - 1 file changed, 1 deletion(-) diff --git a/gatherling/admin/db-upgrade.php b/gatherling/admin/db-upgrade.php index d8488210..6d5e4558 100644 --- a/gatherling/admin/db-upgrade.php +++ b/gatherling/admin/db-upgrade.php @@ -11,7 +11,6 @@ function main(): void { Setup::setupDatabase(); - Setup::setupTestDatabase(); echo 'done'; } From 53b5e6861515e773434f1e2fc7c655c957a9b4f4 Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Sun, 8 Sep 2024 14:16:33 -0700 Subject: [PATCH 3/3] Fix frequent Gatherling logouts by introducing the concept of db-held sessions Despite a bunch of monkeying around with a cookie_lifetime setting and ini_set calls, gatherling.com generally logs you out after 24 minutes of inactivity. The root cause of this problem is abusing the PHP session and trying to make it a thing that lives for 60 days instead of 24 minutes/until you close your browser (the default). The reason extending the session does not work out despite the messing around with ini_set and so on is because most unix-based installs of PHP add a cron job that checks every "active" php.ini on the sever for the session timeout, declares the shortest time found the winner, and kills all older sessions (making the files storing them empty). The fix here is to let PHP sessions be PHP sessions. We no longer try to make them live 60 days. Instead we cookie you with a remember_me cookie against which we store your session details in the database. This cookie lives for 60 days, a fact which is also enforced in the database. If either the cookie or the db row go away then your session will no longer be persisted. We update expiry in both places every time you complete a request in a shutdown function. So you'll stay logged in forever as long as you visit every sixty days. Because there are 150 mentions of "session" in the existing codebase I went for an implementation that is agnostic about what is actually IN the session. We just JSON encode whatever it is and put it in the db. Eventually perhaps all session access will flow through Gatherling\Auth\Session and we can be a bit less generic about it all. We delete all expired sessions on every request from every user. If this ends up being unnecessarily busy we can create a cron job or something similar. Because it will PHP Fatal Error before it runs in web you have to run db-upgrade from the commandline for this migration. The remember_me cookie lives forever and any attempt to remove it will just result in recreation. Even if it's just remembering you are an anonyous person and your session is empty. If that ends up being unnecessarily busy we can revisit. We store expiry in the database not "last active" although either can be derived from the other by adding/substrcacting 60 days. --- gatherling/Auth/Session.php | 74 +++++++++++++++++++++++++++ gatherling/Data/sql/migrations/55.sql | 6 +++ gatherling/config.php.docker | 3 -- gatherling/config.php.example | 3 -- gatherling/lib.php | 8 +-- 5 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 gatherling/Auth/Session.php create mode 100644 gatherling/Data/sql/migrations/55.sql diff --git a/gatherling/Auth/Session.php b/gatherling/Auth/Session.php new file mode 100644 index 00000000..37c5ad41 --- /dev/null +++ b/gatherling/Auth/Session.php @@ -0,0 +1,74 @@ += NOW()'; + $args = ['token' => $token]; + $details = DB::value($sql, $args); + return $details ? json_decode($details, true) : []; + } + + private static function save(): void + { + if (isset($_COOKIE['remember_me'])) { + $token = $_COOKIE['remember_me']; + } else { + $token = bin2hex(random_bytes(32)); + } + // Force to object so that we get '{}' instead of '[]' when empty + $details = json_encode((object) $_SESSION); + $expiry = time() + self::$LIFETIME; + $sql = ' + INSERT INTO + sessions (token, details, expiry) + VALUES + (:token, :details, FROM_UNIXTIME(:expiry)) + ON DUPLICATE KEY UPDATE + details = :details, + expiry = FROM_UNIXTIME(:expiry)'; + $args = [ + 'token' => $token, + 'details' => $details, + 'expiry' => $expiry, + ]; + DB::execute($sql, $args); + setcookie('remember_me', $token, $expiry, '/'); + $sql = 'DELETE FROM sessions WHERE expiry < NOW()'; + DB::execute($sql); + } +} diff --git a/gatherling/Data/sql/migrations/55.sql b/gatherling/Data/sql/migrations/55.sql new file mode 100644 index 00000000..6bb80622 --- /dev/null +++ b/gatherling/Data/sql/migrations/55.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS sessions ( + id INT AUTO_INCREMENT PRIMARY KEY, + token VARCHAR(64) UNIQUE NOT NULL, + details TEXT NOT NULL, + expiry TIMESTAMP NOT NULL +); diff --git a/gatherling/config.php.docker b/gatherling/config.php.docker index a8b521aa..8042e806 100644 --- a/gatherling/config.php.docker +++ b/gatherling/config.php.docker @@ -24,9 +24,6 @@ $CONFIG['style'] = "ChandraNeue"; # A description for the ical calendar which is accessible at calendar.php $CONFIG['calendar_description'] = "a description for the events calendar"; -# How long to store session cookies in seconds -$CONFIG['cookie_lifetime'] = 5184000; - # API Key for Brevo email sending (password reset) $CONFIG['brevo_api_key'] = 'xkeysib-foobar-baz'; diff --git a/gatherling/config.php.example b/gatherling/config.php.example index 4e19ae47..6252f497 100644 --- a/gatherling/config.php.example +++ b/gatherling/config.php.example @@ -29,9 +29,6 @@ $CONFIG['style'] = "ChandraNeue"; # A description for the ical calendar which is accessible at calendar.php $CONFIG['calendar_description'] = "a description for the events calendar"; -# How long to store session cookies in seconds -$CONFIG['cookie_lifetime'] = 5184000; - # API Key for Brevo email sending (password reset) $CONFIG['brevo_api_key'] = 'xkeysib-foobar-baz'; diff --git a/gatherling/lib.php b/gatherling/lib.php index 545aa2fb..6911fde7 100644 --- a/gatherling/lib.php +++ b/gatherling/lib.php @@ -1,21 +1,17 @@