Coverage for gws-app/gws/plugin/auth_session_manager/sqlite/__init__.py: 97%
65 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 12:46 +0200
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 12:46 +0200
1from typing import Optional
3import gws
4import gws.base.auth
5import gws.lib.datetimex as dtx
6import gws.lib.jsonx
7import gws.lib.sqlitex
9gws.ext.new.authSessionManager('sqlite')
11_CLEANUP_INTERVAL = 600
12_TOUCH_INTERVAL = 60
15class Config(gws.base.auth.session_manager.Config):
16 """Configuration for sqlite sessions"""
18 path: Optional[str]
19 """Session storage path."""
22class Object(gws.base.auth.session_manager.Object):
23 dbPath: str
24 table = 'sessions'
26 def configure(self):
27 ver = self.root.specs.version.rpartition('.')[0]
28 self.dbPath = self.cfg('path', default=f'{gws.c.MISC_DIR}/sessions.{ver}.sqlite')
30 ##
32 _cleanupTime = 0
34 def cleanup(self):
35 self._db().execute(
36 f'DELETE FROM {self.table} WHERE updated < :last_time OR created < :max_time',
37 last_time=gws.u.stime() - self.lifeTime,
38 max_time=(gws.u.stime() - self.maxLifeTime) if self.maxLifeTime > 0 else 0,
39 )
40 self._cleanupTime = gws.u.stime()
42 def create(self, method, user, data=None):
43 if gws.u.stime() > self._cleanupTime + _CLEANUP_INTERVAL:
44 self.cleanup()
46 am = self.root.app.authMgr
47 uid = gws.u.random_string(64)
49 self._db().insert(self.table, dict(
50 uid=uid,
51 method_uid=method.uid,
52 user_uid=user.uid,
53 str_user=am.serialize_user(user),
54 str_data=gws.lib.jsonx.to_string(data or {}),
55 created=gws.u.stime(),
56 updated=gws.u.stime(),
57 ))
59 return self.get(uid)
61 def delete(self, sess):
62 self._db().execute(f'DELETE FROM {self.table} WHERE uid=:uid', uid=sess.uid)
64 def delete_all(self):
65 self._db().execute(f'DELETE FROM {self.table}')
67 def get(self, uid):
68 rs = self._db().select(
69 f'SELECT * FROM {self.table} WHERE uid=:uid AND updated >= :last_time AND created >= :max_time',
70 uid=uid,
71 last_time=gws.u.stime() - self.lifeTime,
72 max_time=(gws.u.stime() - self.maxLifeTime) if self.maxLifeTime > 0 else 0,
73 )
74 if len(rs) == 1:
75 return self._session(rs[0])
77 def list_all(self):
78 return [
79 self._session(rec)
80 for rec in self._db().select(f'SELECT * FROM {self.table}')
81 ]
83 def save(self, sess):
84 if not sess.isChanged:
85 return
87 self._db().execute(
88 f'UPDATE {self.table} SET str_data=:str_data, updated=:updated WHERE uid=:uid',
89 str_data=gws.lib.jsonx.to_string(sess.data or {}),
90 updated=gws.u.stime(),
91 uid=sess.uid
92 )
94 sess.isChanged = False
96 def touch(self, sess):
97 if sess.isChanged:
98 return self.save(sess)
100 # throttle updates to avoid excessive writes on high-traffic sessions
101 if gws.u.stime() - dtx.to_timestamp(sess.updated) < _TOUCH_INTERVAL:
102 return
104 self._db().execute(
105 f'UPDATE {self.table} SET updated=:updated WHERE uid=:uid',
106 updated=gws.u.stime(),
107 uid=sess.uid
108 )
110 ##
112 def _session(self, rec):
113 am = self.root.app.authMgr
114 r = gws.u.to_dict(rec)
115 usr = am.unserialize_user(r['str_user'])
116 if not usr:
117 gws.log.error(f'invalid user in session {r["uid"]!r}')
118 self._db().execute(f'DELETE FROM {self.table} WHERE uid=:uid', uid=r['uid'])
119 usr = am.guestUser
120 return gws.base.auth.session.Object(
121 uid=r['uid'],
122 method=am.get_method(r['method_uid']),
123 user=usr,
124 data=gws.lib.jsonx.from_string(r['str_data']),
125 created=dtx.from_timestamp(r['created']),
126 updated=dtx.from_timestamp(r['updated']),
127 )
129 ##
131 _sqlitex: gws.lib.sqlitex.Object
133 def _db(self):
134 if getattr(self, '_sqlitex', None) is None:
135 ddl = f'''
136 CREATE TABLE IF NOT EXISTS {self.table} (
137 uid TEXT NOT NULL PRIMARY KEY,
138 method_uid TEXT NOT NULL,
139 user_uid TEXT NOT NULL,
140 str_user TEXT NOT NULL,
141 str_data TEXT NOT NULL,
142 created INTEGER NOT NULL,
143 updated INTEGER NOT NULL
144 )
145 '''
146 self._sqlitex = gws.lib.sqlitex.Object(self.dbPath, ddl)
147 return self._sqlitex