Skip to content

Commit 7e8db9a

Browse files
authored
Extract most UserSession DatastoreDB access methods. (#8733)
1 parent b4cf93d commit 7e8db9a

File tree

2 files changed

+75
-43
lines changed

2 files changed

+75
-43
lines changed

app/lib/account/backend.dart

Lines changed: 74 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,8 @@ class AccountBackend {
428428
// try to update old session first
429429
if (oldSession != null) {
430430
final rs = await withRetryTransaction(_db, (tx) async {
431-
final session = await tx.lookupOrNull<UserSession>(oldSession.key);
431+
final session =
432+
await tx.userSessions.lookupOrNull(oldSession.sessionId);
432433
if (session == null) {
433434
return null;
434435
}
@@ -462,8 +463,7 @@ class AccountBackend {
462463
throw AuthenticationException.failed();
463464
}
464465
final data = await withRetryTransaction(_db, (tx) async {
465-
final session = await tx.lookupOrNull<UserSession>(
466-
_db.emptyKey.append(UserSession, id: sessionId));
466+
final session = await tx.userSessions.lookupOrNull(sessionId);
467467
if (session == null || session.isExpired()) {
468468
throw AuthenticationException.failed('Session has been expired.');
469469
}
@@ -570,36 +570,24 @@ class AccountBackend {
570570
/// Deletes the session entry if it has already expired and
571571
/// clears the related cache too.
572572
Future<UserSession?> lookupValidUserSession(String sessionId) async {
573-
final key = _db.emptyKey.append(UserSession, id: sessionId);
574-
final session = await _db.lookupOrNull<UserSession>(key);
573+
final session = await _db.userSessions.lookupOrNull(sessionId);
575574
if (session == null) {
576575
return null;
577576
}
578577
if (session.isExpired()) {
579-
await _db.commit(deletes: [key]);
580-
await cache.userSessionData(sessionId).purge();
578+
await _db.userSessions.expire(session.sessionId);
581579
return null;
582580
}
583581
return session;
584582
}
585583

586-
/// Removes the session data from the Datastore and from cache.
587-
Future<void> invalidateUserSession(String sessionId) async {
588-
final key = _db.emptyKey.append(UserSession, id: sessionId);
589-
try {
590-
await _db.commit(deletes: [key]);
591-
} catch (_) {
592-
// ignore if the entity has been already deleted concurrently
584+
/// Deletes sessions associated with a [userId] or [sessionId].
585+
Future<void> deleteUserSessions({String? userId, String? sessionId}) async {
586+
if (sessionId != null) {
587+
await _db.userSessions.expire(sessionId);
593588
}
594-
await cache.userSessionData(sessionId).purge();
595-
}
596-
597-
/// Scans Datastore for all sessions the user has, and invalidates
598-
/// them all (by deleting the Datastore entry and purging the cache).
599-
Future<void> invalidateAllUserSessions(String userId) async {
600-
final query = _db.query<UserSession>()..filter('userId =', userId);
601-
await for (final session in query.run()) {
602-
await invalidateUserSession(session.sessionId);
589+
if (userId != null) {
590+
await _db.userSessions.expireAllForUserId(userId);
603591
}
604592
}
605593

@@ -608,9 +596,8 @@ class AccountBackend {
608596
final now = clock.now().toUtc();
609597
// account for possible clock skew
610598
final ts = now.subtract(Duration(minutes: 15));
611-
final query = _db.query<UserSession>()..filter('expires <', ts);
612-
final count = await _db.deleteWithQuery(query);
613-
_logger.info('Deleted ${count.deleted} UserSession entries.');
599+
final count = await _db.userSessions.expireAllBeforeTimestamp(ts);
600+
_logger.info('Deleted $count UserSession entries.');
614601
}
615602

616603
/// Updates the moderated status of a user.
@@ -644,7 +631,7 @@ class AccountBackend {
644631
tx.insert(mc);
645632
}
646633
});
647-
await _expireAllSessions(userId);
634+
await _db.userSessions.expireAllForUserId(userId);
648635
await purgeAccountCache(userId: userId);
649636
}
650637

@@ -661,16 +648,6 @@ class AccountBackend {
661648
}
662649
return query.run();
663650
}
664-
665-
// expire all sessions of a given user from datastore and cache
666-
Future<void> _expireAllSessions(String userId) async {
667-
final query = _db.query<UserSession>()..filter('userId =', userId);
668-
final sessionsToDelete = await query.run().toList();
669-
for (final session in sessionsToDelete) {
670-
await _db.commit(deletes: [session.key]);
671-
await cache.userSessionData(session.sessionId).purge();
672-
}
673-
}
674651
}
675652

676653
/// Purge [cache] entries for given [userId].
@@ -682,3 +659,63 @@ Future<void> purgeAccountCache({
682659
cache.publisherPage(userId).purgeAndRepeat(),
683660
]);
684661
}
662+
663+
/// Low-level, narrowly typed data access methods for [UserSession] entity.
664+
extension UserSessionDatastoreDBExt on DatastoreDB {
665+
_UserSessionDataAccess get userSessions => _UserSessionDataAccess(this);
666+
}
667+
668+
extension UserSessionTransactionWrapperExt on TransactionWrapper {
669+
_UserSessionTransactionDataAcccess get userSessions =>
670+
_UserSessionTransactionDataAcccess(this);
671+
}
672+
673+
class _UserSessionDataAccess {
674+
final DatastoreDB _db;
675+
676+
_UserSessionDataAccess(this._db);
677+
678+
Future<UserSession?> lookupOrNull(String sessionId) async {
679+
final key = _db.emptyKey.append(UserSession, id: sessionId);
680+
return await _db.lookupOrNull<UserSession>(key);
681+
}
682+
683+
/// Scans Datastore for all sessions the user has, and invalidates
684+
/// them all (by deleting the Datastore entry and purging the cache).
685+
Future<void> expireAllForUserId(String userId) async {
686+
final query = _db.query<UserSession>()..filter('userId =', userId);
687+
final sessionsToDelete = await query.run().toList();
688+
for (final session in sessionsToDelete) {
689+
await expire(session.sessionId);
690+
}
691+
}
692+
693+
/// Removes the session data from the Datastore and from cache.
694+
Future<void> expire(String sessionId) async {
695+
final key = _db.emptyKey.append(UserSession, id: sessionId);
696+
try {
697+
await _db.commit(deletes: [key]);
698+
} on Exception catch (_) {
699+
// ignore if the entity has been already deleted concurrently
700+
}
701+
await cache.userSessionData(sessionId).purge();
702+
}
703+
704+
/// Removes the session data that has expiry before [ts].
705+
Future<int> expireAllBeforeTimestamp(DateTime ts) async {
706+
final query = _db.query<UserSession>()..filter('expires <', ts);
707+
final count = await _db.deleteWithQuery(query);
708+
return count.deleted;
709+
}
710+
}
711+
712+
class _UserSessionTransactionDataAcccess {
713+
final TransactionWrapper _tx;
714+
715+
_UserSessionTransactionDataAcccess(this._tx);
716+
717+
Future<UserSession?> lookupOrNull(String sessionId) async {
718+
final key = _tx.emptyKey.append(UserSession, id: sessionId);
719+
return await _tx.lookupOrNull<UserSession>(key);
720+
}
721+
}

app/lib/frontend/handlers/account.dart

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,7 @@ Future<shelf.Response> invalidateSessionHandler(shelf.Request request) async {
138138
final userId = requestContext.authenticatedUserId;
139139
// Invalidate the server-side session object, in case the user signed out because
140140
// the local cookie store was compromised.
141-
if (sessionId != null) {
142-
await accountBackend.invalidateUserSession(sessionId);
143-
}
144-
if (userId != null) {
145-
await accountBackend.invalidateAllUserSessions(userId);
146-
}
141+
await accountBackend.deleteUserSessions(userId: userId, sessionId: sessionId);
147142
return jsonResponse(
148143
{},
149144
// Clear cookie, so we don't have to lookup an invalid sessionId.

0 commit comments

Comments
 (0)