store sessions in the DB
This commit is contained in:
@@ -8,7 +8,8 @@ regular user.
|
||||
Mutations are append-only events with unique ids — the same replication
|
||||
model as ``BillingLedger`` — so accounts and API keys converge across the
|
||||
tracker hive via gossip, and every dashboard can serve registration/login.
|
||||
Sessions are deliberately local to each tracker (bearer tokens in memory).
|
||||
Sessions are local to each tracker and persisted so dashboard cookies survive
|
||||
tracker restarts.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -115,6 +116,8 @@ class AccountStore:
|
||||
"account_id": account_id,
|
||||
"expires": time.time() + SESSION_TTL,
|
||||
}
|
||||
self._dirty = True
|
||||
self.save_to_db()
|
||||
return token
|
||||
|
||||
def session_account(self, token: str | None) -> dict | None:
|
||||
@@ -134,7 +137,9 @@ class AccountStore:
|
||||
if not token:
|
||||
return
|
||||
with self._lock:
|
||||
self._sessions.pop(token, None)
|
||||
if self._sessions.pop(token, None) is not None:
|
||||
self._dirty = True
|
||||
self.save_to_db()
|
||||
|
||||
# ---- API keys ----
|
||||
|
||||
@@ -271,6 +276,10 @@ class AccountStore:
|
||||
"CREATE TABLE IF NOT EXISTS account_events "
|
||||
"(event_id TEXT PRIMARY KEY, payload TEXT NOT NULL, ts REAL NOT NULL)"
|
||||
)
|
||||
con.execute(
|
||||
"CREATE TABLE IF NOT EXISTS account_sessions "
|
||||
"(token TEXT PRIMARY KEY, account_id TEXT NOT NULL, expires REAL NOT NULL)"
|
||||
)
|
||||
con.commit()
|
||||
con.close()
|
||||
|
||||
@@ -279,6 +288,10 @@ class AccountStore:
|
||||
rows = con.execute(
|
||||
"SELECT payload FROM account_events ORDER BY ts, event_id"
|
||||
).fetchall()
|
||||
session_rows = con.execute(
|
||||
"SELECT token, account_id, expires FROM account_sessions WHERE expires >= ?",
|
||||
(time.time(),),
|
||||
).fetchall()
|
||||
con.close()
|
||||
with self._lock:
|
||||
for (payload,) in rows:
|
||||
@@ -288,6 +301,11 @@ class AccountStore:
|
||||
continue
|
||||
if event.get("id") not in self._seen_event_ids:
|
||||
self._apply_locked(event)
|
||||
self._sessions = {
|
||||
token: {"account_id": account_id, "expires": float(expires)}
|
||||
for token, account_id, expires in session_rows
|
||||
if account_id in self._accounts
|
||||
}
|
||||
self._dirty = False
|
||||
|
||||
def save_to_db(self) -> None:
|
||||
@@ -297,11 +315,21 @@ class AccountStore:
|
||||
if not self._dirty:
|
||||
return
|
||||
events = list(self._event_log)
|
||||
sessions = [
|
||||
(token, session["account_id"], float(session["expires"]))
|
||||
for token, session in self._sessions.items()
|
||||
if session["expires"] >= time.time()
|
||||
]
|
||||
self._dirty = False
|
||||
con = sqlite3.connect(self._db_path) # type: ignore[arg-type]
|
||||
con.executemany(
|
||||
"INSERT OR IGNORE INTO account_events (event_id, payload, ts) VALUES (?, ?, ?)",
|
||||
[(e["id"], json.dumps(e), float(e.get("ts", 0.0))) for e in events],
|
||||
)
|
||||
con.execute("DELETE FROM account_sessions")
|
||||
con.executemany(
|
||||
"INSERT INTO account_sessions (token, account_id, expires) VALUES (?, ?, ?)",
|
||||
sessions,
|
||||
)
|
||||
con.commit()
|
||||
con.close()
|
||||
|
||||
Reference in New Issue
Block a user