threadListAvatars: dynamic unauthorized forums

This is an implementation of point 3 in the "Idea" section of the
following doc: go/eu7T9m (public link available in the linked bug).

Paraphrasing the previous document:
This change adds code to maintain a list of forums from which the
extension couldn't load any thread, which probably correspond to private
forums. When a forum is in this list, avatars are no longer retrieved
from threads in that forum until the entry expires.

Bug: 2
Change-Id: I05e7b02818181f410855948e1af6ec75fc7c792b
diff --git a/src/contentScripts/communityConsole/avatars.js b/src/contentScripts/communityConsole/avatars.js
index 3b27333..5e5eeee 100644
--- a/src/contentScripts/communityConsole/avatars.js
+++ b/src/contentScripts/communityConsole/avatars.js
@@ -58,7 +58,9 @@
   // the thread belongs to a known private forum.
   shouldRetrieveAvatars(thread) {
     return this.getPrivateForums().then(privateForums => {
-      return !privateForums.includes(thread.forum);
+      if (privateForums.includes(thread.forum)) return false;
+
+      return this.db.isForumUnauthorized(thread.forum).then(res => !res);
     });
   }
 
@@ -90,8 +92,16 @@
                // Due to the fact that we have to call this endpoint
                // anonymously, this means we can't retrieve information about
                // threads in private forums.
-               /* authentication = */ false)
-        .then(data => {
+               /* authentication = */ false, /* authuser = */ 0,
+               /* returnUnauthorizedStatus = */ true)
+        .then(response => {
+          if (response.unauthorized)
+            return this.db.putUnauthorizedForum(thread.forum).then(() => {
+              throw new Error('Permission denied to load thread.');
+            });
+
+          var data = response.body;
+
           var numMessages = data?.['1']?.['8'];
           if (numMessages === undefined)
             throw new Error(