public async Task TestSendToAccount()
        {
            Dictionary <long, bool> sent = await CreateSessions(database, connections,
                                                                (alice, 1, true), (alice, 2, false), (alice, 3, true), (alice, 4, true))
                                           .ConfigureAwait(false);

            var packet = packets.New <P0ACreateChannel>();

            Assert.IsTrue(connections.TryGet(1, out IClient exclude), "Client disappeared from ConnectionsService");
            await delivery.StartSendToAccount(packet, alice, exclude).ConfigureAwait(false);

            Assert.IsFalse(sent[1]);
            Assert.IsFalse(sent[2]);
            Assert.IsTrue(sent[3]);
            Assert.IsTrue(sent[4]);
        }
Example #2
0
        public void TestReplaceOnDuplicate()
        {
            IClient client1 = new FakeClient {
                AccountId = 1, SessionId = 1
            };
            IClient client2 = new FakeClient {
                AccountId = 1, SessionId = 1
            };
            var connections = new ConnectionsService();

            IClient replaced1 = connections.Add(client1);

            Assert.IsNull(replaced1);

            Assert.IsTrue(connections.TryGet(1, out IClient out1));
            Assert.IsTrue(ReferenceEquals(client1, out1));

            IClient replaced2 = connections.Add(client2);

            Assert.IsTrue(ReferenceEquals(client1, replaced2));

            Assert.IsTrue(connections.TryGet(1, out IClient out2));
            Assert.IsTrue(ReferenceEquals(client2, out2));
        }
        public override async ValueTask Handle(P04DeleteAccount packet)
        {
            var response = Packets.New <P05DeleteAccountResponse>();

            byte[] passwordHash;
            using (var csp = SHA256.Create())
                passwordHash = csp.ComputeHash(packet.KeyHash);

            // EF Core converts the C# == operator to = in SQL which compares the contents of byte arrays
            Account account = await Database.Accounts.AsTracking()
                              .SingleOrDefaultAsync(a => a.AccountId == Client.AccountId && a.PasswordHash == passwordHash).ConfigureAwait(false);

            if (account == null)
            {
                response.StatusCode = DeleteAccountStatus.InvalidCredentials;
                await Client.Send(response).ConfigureAwait(false);

                return;
            }

            account.DeletionTime = DateTime.Now;
            try
            {
                await Database.SaveChangesAsync().ConfigureAwait(false);
            }
            catch (DbUpdateConcurrencyException) // Account is being deleted by a another handler
            {
                // We can safely send a response to the client because the second handler is waiting for this one to return
                response.StatusCode = DeleteAccountStatus.Success;
                await Client.Send(response).ConfigureAwait(false);

                await Client.DisposeAsync(waitForHandling : false).ConfigureAwait(false);
            }


            // Delete mail confirmations and sessions
            // This will prevent clients from logging in with the deleted account
            MailConfirmation[] mailConfirmations = await Database.MailConfirmations.AsQueryable()
                                                   .Where(c => c.AccountId == Client.AccountId)
                                                   .ToArrayAsync().ConfigureAwait(false);

            Session[] sessions = await Database.Sessions.AsQueryable()
                                 .Where(s => s.AccountId == Client.AccountId)
                                 .ToArrayAsync().ConfigureAwait(false);

            Database.MailConfirmations.RemoveRange(mailConfirmations);
            Database.Sessions.RemoveRange(sessions);
            await Database.SaveChangesAsync().ConfigureAwait(false);


            // Kick all sessions
            var tasks = new List <Task>();

            foreach (Session session in sessions)
            {
                if (connections.TryGet(session.SessionId, out IClient client) && !ReferenceEquals(client, Client))
                {
                    tasks.Add(client.DisposeAsync(true, true, false).AsTask());
                }
            }
            await Task.WhenAll(tasks).ConfigureAwait(false);


            // Do all remaining database operations
            ChannelMember[] memberships = await Database.ChannelMembers.AsQueryable()
                                          .Where(m => m.AccountId == Client.AccountId)
                                          .ToArrayAsync().ConfigureAwait(false);

            Database.ChannelMembers.RemoveRange(memberships);
            await Database.SaveChangesAsync().ConfigureAwait(false);


            // Finish handling and close last connection
            response.StatusCode = DeleteAccountStatus.Success;
            await Client.Send(response).ConfigureAwait(false);

            await Client.DisposeAsync(waitForHandling : false).ConfigureAwait(false);
        }