using MongoDB.Driver; using MongoDB.Entities; using OpenIddict.Abstractions; using PrivaPub.Models.User; using PrivaPub.StaticServices; namespace PrivaPub.Services { public interface IRootSessions { Task Revoke(string rootId, CancellationToken token); Task End(string rootId, string sessionId, CancellationToken token); } // Ends everything a root is signed in with: its /clientapi tokens, through a new SessionStamp (checked by JwtEvents), // and the Mastodon API tokens and authorizations of each of its personas. Used when its password is changed or // recovered, when it signs out everywhere and when it is deleted. End signs out one session only: the device that // asked (its Mastodon tokens it revokes itself). public class RootSessions : IRootSessions { readonly DbEntities _dbEntities; readonly IOpenIddictTokenManager _tokens; readonly IOpenIddictAuthorizationManager _authorizations; readonly AuthTokenManager _authTokens; public RootSessions(DbEntities dbEntities, IOpenIddictTokenManager tokens, IOpenIddictAuthorizationManager authorizations, AuthTokenManager authTokens) { _dbEntities = dbEntities; _tokens = tokens; _authorizations = authorizations; _authTokens = authTokens; } public async Task Revoke(string rootId, CancellationToken token) { await DB.Default.Update().MatchID(rootId) .Modify(u => u.CredentialsChangedAt, DateTime.UtcNow) .Modify(u => u.SessionStamp, Guid.NewGuid().ToString("N")) .Modify(u => u.EndedSessions, new List())//the new stamp ends those too .ExecuteAsync(token); foreach (var link in await _dbEntities.RootToAvatars.Match(ra => ra.RootId == rootId).ExecuteAsync(token)) { await _tokens.RevokeBySubjectAsync(link.AvatarId, token); await _authorizations.RevokeBySubjectAsync(link.AvatarId, token); } } // remembered as long as one of its tokens could still be valid; the ones that have expired are forgotten meanwhile public async Task End(string rootId, string sessionId, CancellationToken token) { var now = DateTime.UtcNow; await DB.Default.Update().MatchID(rootId) .Modify(b => b.PullFilter(u => u.EndedSessions, s => s.Until < now)) .ExecuteAsync(token); await DB.Default.Update().MatchID(rootId) .Modify(b => b.Push(u => u.EndedSessions, new EndedSession { Sid = sessionId, Until = now + _authTokens.Lifetime })) .ExecuteAsync(token); } } }