Repository: getindiekit/indiekit (js)

Issue:
Support Microsub (and unknown) scopes in authorisation requests
### Description of the bug

Using Indiekit’s IndieAuth endpoint with a Microsub client will fail to show the scopes it’s requesting, or allow the user to edit them. That’s because, right now, Indiekit only recognises the following Micropub scopes: `create`, `update`, `draft`, `media`, `delete` (Microsub uses the following scopes: `read`, `follow`, `mute`, `block`, `channels`).

### Expected behaviour

All scopes requested should be shown on the consent screen.

### Additional context

Consider changing the logic from showing all possible scopes, and checking those requested, to showing all requested scopes, and having them all checked by default (it’s senseless giving an application a permission it doesn’t want, need or recognise).

Need to do the following:

- (Only) show all requested scopes
- Add localistions for all known Microsub scopes
- Change ‘Publishing permissions’ to ‘Requested permissions’ or similar
- Provide a way to fallback to scope name, if no localised label for a scope exists

Pull request:
Only show requested scopes, indicate which are not supported
Fixes #715.

Previously, the consent screen (and therefore the tokens generated) only allowed users to check 5 pre-defined scopes: `create`, `update`, `draft`, `delete` and `media`. Other scopes that were requested couldn’t be seen or (un)checked, and there was no way of showing which scopes were unsupported by Indiekit.

This PR:

- Changes the consent screen so that only requested scopes are shown
- Localisations have been added for [all known scopes](https://indieweb.org/scope); if a scope is requested that is not known, then the scope name is shown instead of a localised string
- `profile` and `email` are not supported by the IndieAuth endpoint, so these are shown, but disabled and marked as unsupported.

## Examples

<img width="560" alt="Consent screen for Quill." src="https://github.com/getindiekit/indiekit/assets/813383/c3eed99d-582a-4281-8aa2-08802687f7e1">
<img width="560" alt="Consent screen for Aperture." src="https://github.com/getindiekit/indiekit/assets/813383/03275c8a-253f-4355-bfdf-86e357d057b0">
<img width="560" alt="Consent screen for Monocle." src="https://github.com/getindiekit/indiekit/assets/813383/55a205bd-17db-490a-99da-7dd280fd2a02">
<img width="560" alt="Consent screen for Sparkles." src="https://github.com/getindiekit/indiekit/assets/813383/bf791b04-261f-4619-9a26-0651fe41b039">


Patch:
diff --git a/packages/endpoint-auth/lib/controllers/metadata.js b/packages/endpoint-auth/lib/controllers/metadata.js
index 1a2e724e..c60498eb 100644
--- a/packages/endpoint-auth/lib/controllers/metadata.js
+++ b/packages/endpoint-auth/lib/controllers/metadata.js
@@ -1,4 +1,4 @@
-import { scopes } from "../scope.js";
+import { supportedScopes } from "../scope.js";
 
 export const metadataController = (request, response) => {
   const { application } = request.app.locals;
@@ -10,7 +10,7 @@ export const metadataController = (request, response) => {
     token_endpoint: application.tokenEndpoint,
     code_challenge_methods_supported: ["S256"],
     response_types_supported: ["code"],
-    scopes_supported: scopes,
+    scopes_supported: supportedScopes,
     service_documentation: application.authorizationEndpoint,
     ui_locales_supported: application.localeUsed,
   };
diff --git a/packages/endpoint-auth/lib/scope.js b/packages/endpoint-auth/lib/scope.js
index 58c7db81..62c36861 100644
--- a/packages/endpoint-auth/lib/scope.js
+++ b/packages/endpoint-auth/lib/scope.js
@@ -1,4 +1,24 @@
-export const scopes = ["create", "update", "draft", "media", "delete"];
+export const scopes = {
+  // IndieAuth scopes
+  email: { supported: false },
+  profile: { supported: false },
+  // Micropub scopes
+  create: { supported: true },
+  draft: { supported: true },
+  update: { supported: true },
+  delete: { supported: true },
+  media: { supported: true },
+  // Microsub scopes
+  read: { supported: true },
+  follow: { supported: true },
+  mute: { supported: true },
+  block: { supported: true },
+  channels: { supported: true },
+};
+
+export const supportedScopes = Object.entries(scopes)
+  .filter(([, value]) => value.supported)
+  .map(([key]) => key);
 
 /**
  * Get `items` object for checkboxes component
@@ -7,9 +27,18 @@ export const scopes = ["create", "update", "draft", "media", "delete"];
  * @returns {object} Items for checkboxes component
  */
 export function getScopeItems(scope, response) {
-  return scopes.map((value) => ({
-    label: response.locals.__(`scope.${value}.label`),
+  const localisedScopes = Object.keys(scopes);
+  const requestedScopes = scope ? scope.split(" ") : [];
+
+  return requestedScopes.map((value) => ({
+    label: localisedScopes.includes(value)
+      ? response.locals.__(`scope.${value}.label`)
+      : value,
+    checked: supportedScopes.includes(value) && requestedScopes.includes(value),
+    ...(!supportedScopes.includes(value) && {
+      disabled: true,
+      hint: response.locals.__("scope.notSupported.hint"),
+    }),
     value,
-    checked: scope?.includes(value),
   }));
 }
diff --git a/packages/indiekit/locales/de.json b/packages/indiekit/locales/de.json
index fd47729a..e04de33d 100644
--- a/packages/indiekit/locales/de.json
+++ b/packages/indiekit/locales/de.json
@@ -9,6 +9,12 @@
     "options": "Optionen"
   },
   "scope": {
+    "block": {
+      "label": "Nutzer blockieren"
+    },
+    "channels": {
+      "label": "Kanäle verwalten"
+    },
     "create": {
       "label": "Beiträge erstellen"
     },
@@ -18,13 +24,31 @@
     "draft": {
       "label": "Nur Beitragsentwürfe erstellen und aktualisieren"
     },
-    "label": "Veröffentlichungsberechtigungen",
+    "email": {
+      "label": "E-Mail-Adresse"
+    },
+    "follow": {
+      "label": "Verwalten Sie folgende Liste"
+    },
+    "label": "Angeforderte Berechtigungen",
     "media": {
       "label": "Medien hochladen"
     },
+    "mute": {
+      "label": "Benutzer stummschalten"
+    },
     "none": {
       "label": "Sie haben keine Veröffentlichungsberechtigungen"
     },
+    "notSupported": {
+      "hint": "Nicht unterstützt"
+    },
+    "profile": {
+      "label": "Informationen zum Profil"
+    },
+    "read": {
+      "label": "Kanäle lesen"
+    },
     "update": {
       "label": "Beiträge aktualisieren"
     }
diff --git a/packages/indiekit/locales/en.json b/packages/indiekit/locales/en.json
index 4668087f..7b54697a 100644
--- a/packages/indiekit/locales/en.json
+++ b/packages/indiekit/locales/en.json
@@ -20,24 +20,48 @@
     }
   },
   "scope": {
-    "label": "Publishing permissions",
+    "label": "Requested permissions",
+    "email": {
+      "label": "Email address"
+    },
+    "profile": {
+      "label": "Profile information"
+    },
     "create": {
       "label": "Create posts"
     },
-    "delete": {
-      "label": "Delete posts and media"
-    },
     "draft": {
       "label": "Create and update draft posts only"
     },
+    "update": {
+      "label": "Update posts"
+    },
+    "delete": {
+      "label": "Delete posts and media"
+    },
     "media": {
       "label": "Upload media"
     },
+    "read": {
+      "label": "Read channels"
+    },
+    "follow": {
+      "label": "Manage following list"
+    },
+    "mute": {
+      "label": "Mute users"
+    },
+    "block": {
+      "label": "Block users"
+    },
+    "channels": {
+      "label": "Manage channels"
+    },
     "none": {
       "label": "You don’t have any publishing permissions"
     },
-    "update": {
-      "label": "Update posts"
+    "notSupported": {
+      "hint": "Not supported"
     }
   },
   "status": {
diff --git a/packages/indiekit/locales/es-419.json b/packages/indiekit/locales/es-419.json
index 23e62cef..fe6255d0 100644
--- a/packages/indiekit/locales/es-419.json
+++ b/packages/indiekit/locales/es-419.json
@@ -9,6 +9,12 @@
     "options": "Opciones"
   },
   "scope": {
+    "block": {
+      "label": "Bloquear usuarios"
+    },
+    "channels": {
+      "label": "Gestiona los canales"
+    },
     "create": {
       "label": "Crear publicaciones"
     },
@@ -18,13 +24,31 @@
     "draft": {
       "label": "Crear y actualizar solo borradores de publicaciones"
     },
-    "label": "Permisos de publicación",
+    "email": {
+      "label": "Dirección de email"
+    },
+    "follow": {
+      "label": "Administrar la lista de seguimiento"
+    },
+    "label": "Permisos solicitados",
     "media": {
       "label": "Subir contenido multimedia"
     },
+    "mute": {
+      "label": "Silenciar usuarios"
+    },
     "none": {
       "label": "No tiene ningún permiso de publicación"
     },
+    "notSupported": {
+      "hint": "No se admite"
+    },
+    "profile": {
+      "label": "Información de perfil"
+    },
+    "read": {
+      "label": "Leer canales"
+    },
     "update": {
       "label": "Actualizar publicaciones"
     }
diff --git a/packages/indiekit/locales/es.json b/packages/indiekit/locales/es.json
index 51e61a89..c1dfa2d6 100644
--- a/packages/indiekit/locales/es.json
+++ b/packages/indiekit/locales/es.json
@@ -9,6 +9,12 @@
     "options": "Opciones"
   },
   "scope": {
+    "block": {
+      "label": "Bloquear usuarios"
+    },
+    "channels": {
+      "label": "Gestiona los canales"
+    },
     "create": {
       "label": "Crear publicaciones"
     },
@@ -18,13 +24,31 @@
     "draft": {
       "label": "Crea y actualiza solo borradores de publicaciones"
     },
-    "label": "Permisos de publicación",
+    "email": {
+      "label": "Dirección de email"
+    },
+    "follow": {
+      "label": "Administrar la lista de seguimiento"
+    },
+    "label": "Permisos solicitados",
     "media": {
       "label": "Cargar archivos multimedia"
     },
+    "mute": {
+      "label": "Silenciar usuarios"
+    },
     "none": {
       "label": "No tienes ningún permiso de publicación"
     },
+    "notSupported": {
+      "hint": "No se admite"
+    },
+    "profile": {
+      "label": "Información de perfil"
+    },
+    "read": {
+      "label": "Leer canales"
+    },
     "update": {
       "label": "Actualizar publicaciones"
     }
diff --git a/packages/indiekit/locales/fr.json b/packages/indiekit/locales/fr.json
index a06d7f9b..245cedb2 100644
--- a/packages/indiekit/locales/fr.json
+++ b/packages/indiekit/locales/fr.json
@@ -9,6 +9,12 @@
     "options": "Paramètres"
   },
   "scope": {
+    "block": {
+      "label": "Bloquer les utilisateurs"
+    },
+    "channels": {
+      "label": "Gérer les chaînes"
+    },
     "create": {
       "label": "Créer des publications"
     },
@@ -18,13 +24,31 @@
     "draft": {
       "label": "Créer et mettre à jour des brouillons de publications uniquement"
     },
-    "label": "Autorisations de publication",
+    "email": {
+      "label": "Addresse email"
+    },
+    "follow": {
+      "label": "Gérer la liste suivante"
+    },
+    "label": "Autorisations demandées",
     "media": {
       "label": "Téléverser des médias"
     },
+    "mute": {
+      "label": "Désactiver les utilisateurs"
+    },
     "none": {
       "label": "Vous n’avez aucune autorisation pour publier"
     },
+    "notSupported": {
+      "hint": "Non pris en charge"
+    },
+    "profile": {
+      "label": "Informations sur le profil"
+    },
+    "read": {
+      "label": "Lire les chaînes"
+    },
     "update": {
       "label": "Mettre à jour les publications"
     }
@@ -72,4 +96,4 @@
     },
     "title": "Statut du serveur"
   }
-}
\ No newline at end of file
+}
diff --git a/packages/indiekit/locales/id.json b/packages/indiekit/locales/id.json
index 515663c6..0633f44e 100644
--- a/packages/indiekit/locales/id.json
+++ b/packages/indiekit/locales/id.json
@@ -9,6 +9,12 @@
     "options": "Pilihan"
   },
   "scope": {
+    "block": {
+      "label": "Blokir pengguna"
+    },
+    "channels": {
+      "label": "Kelola saluran"
+    },
     "create": {
       "label": "Membuat pos"
     },
@@ -18,13 +24,31 @@
     "draft": {
       "label": "Membuat dan memperbarui draft posting saja"
     },
-    "label": "Izin publikasi",
+    "email": {
+      "label": "Alamat Email"
+    },
+    "follow": {
+      "label": "Kelola daftar berikut"
+    },
+    "label": "Izin yang diminta",
     "media": {
       "label": "Unggah media"
     },
+    "mute": {
+      "label": "Bisukan pengguna"
+    },
     "none": {
       "label": "Anda tidak memiliki izin penerbitan"
     },
+    "notSupported": {
+      "hint": "Tidak didukung"
+    },
+    "profile": {
+      "label": "Informasi profil"
+    },
+    "read": {
+      "label": "Baca saluran"
+    },
     "update": {
       "label": "Perbarui pos"
     }
@@ -72,4 +96,4 @@
     },
     "title": "Status server"
   }
-}
\ No newline at end of file
+}
diff --git a/packages/indiekit/locales/nl.json b/packages/indiekit/locales/nl.json
index 56651aa6..91c122e1 100644
--- a/packages/indiekit/locales/nl.json
+++ b/packages/indiekit/locales/nl.json
@@ -9,6 +9,12 @@
     "options": "Opties"
   },
   "scope": {
+    "block": {
+      "label": "Gebruikers blokkeren"
+    },
+    "channels": {
+      "label": "Kanalen beheren"
+    },
     "create": {
       "label": "Posten maken"
     },
@@ -18,13 +24,31 @@
     "draft": {
       "label": "Maak en update alleen conceptposts"
     },
-    "label": "Machtigingen voor publicatie",
+    "email": {
+      "label": "Emailadres"
+    },
+    "follow": {
+      "label": "De volgende lijst beheren"
+    },
+    "label": "Permissies gevraagd",
     "media": {
       "label": "Media uploaden"
     },
+    "mute": {
+      "label": "Gebruikers dempen"
+    },
     "none": {
       "label": "Je hebt geen publicatierechten"
     },
+    "notSupported": {
+      "hint": "Niet ondersteund"
+    },
+    "profile": {
+      "label": "Informatie over het profiel"
+    },
+    "read": {
+      "label": "Kanalen lezen"
+    },
     "update": {
       "label": "Posten updaten"
     }
@@ -72,4 +96,4 @@
     },
     "title": "Status server"
   }
-}
\ No newline at end of file
+}
diff --git a/packages/indiekit/locales/pl.json b/packages/indiekit/locales/pl.json
index 094b46f9..2a24ae9a 100644
--- a/packages/indiekit/locales/pl.json
+++ b/packages/indiekit/locales/pl.json
@@ -9,6 +9,12 @@
     "options": "Opcje"
   },
   "scope": {
+    "block": {
+      "label": "Blokuj użytkowników"
+    },
+    "channels": {
+      "label": "Zarządzanie kanałami"
+    },
     "create": {
       "label": "Twórz posty"
     },
@@ -18,13 +24,31 @@
     "draft": {
       "label": "Twórz i aktualizuj tylko wersje robocze postów"
     },
-    "label": "Uprawnienia do publikowania",
+    "email": {
+      "label": "Adres e-mail"
+    },
+    "follow": {
+      "label": "Zarządzaj następującą listą"
+    },
+    "label": "Żądane uprawnienia",
     "media": {
       "label": "Prześlij multimedia"
     },
+    "mute": {
+      "label": "Wycisz użytkowników"
+    },
     "none": {
       "label": "Nie masz żadnych uprawnień do publikowania"
     },
+    "notSupported": {
+      "hint": "Nieobsługiwane"
+    },
+    "profile": {
+      "label": "Informacje o profilu"
+    },
+    "read": {
+      "label": "Odczytaj kanały"
+    },
     "update": {
       "label": "Aktualizuj posty"
     }
@@ -72,4 +96,4 @@
     },
     "title": "Status serwera"
   }
-}
\ No newline at end of file
+}
diff --git a/packages/indiekit/locales/pt.json b/packages/indiekit/locales/pt.json
index 013bd847..bfb63244 100644
--- a/packages/indiekit/locales/pt.json
+++ b/packages/indiekit/locales/pt.json
@@ -9,6 +9,12 @@
     "options": "Opções"
   },
   "scope": {
+    "block": {
+      "label": "Bloquear usuários"
+    },
+    "channels": {
+      "label": "Gerenciar canais"
+    },
     "create": {
       "label": "Crie postagens"
     },
@@ -18,13 +24,31 @@
     "draft": {
       "label": "Crie e atualize somente rascunhos de publicações"
     },
-    "label": "Permissões de publicação",
+    "email": {
+      "label": "Endereço de e-mail"
+    },
+    "follow": {
+      "label": "Gerenciar a lista a seguir"
+    },
+    "label": "Permissões solicitadas",
     "media": {
       "label": "Carregar mídia"
     },
+    "mute": {
+      "label": "Silenciar usuários"
+    },
     "none": {
       "label": "Você não tem nenhuma permissão de publicação"
     },
+    "notSupported": {
+      "hint": "Não suportado"
+    },
+    "profile": {
+      "label": "Informação de Perfil"
+    },
+    "read": {
+      "label": "Leia canais"
+    },
     "update": {
       "label": "Atualizar postagens"
     }
@@ -72,4 +96,4 @@
     },
     "title": "Status do servidor"
   }
-}
\ No newline at end of file
+}
diff --git a/packages/indiekit/locales/sr.json b/packages/indiekit/locales/sr.json
index 0cfeab4c..f753d9cb 100644
--- a/packages/indiekit/locales/sr.json
+++ b/packages/indiekit/locales/sr.json
@@ -9,6 +9,12 @@
     "options": "Opcije"
   },
   "scope": {
+    "block": {
+      "label": "Blokirajte korisnike"
+    },
+    "channels": {
+      "label": "Upravljanje kanalima"
+    },
     "create": {
       "label": "Kreirajte postova"
     },
@@ -18,13 +24,31 @@
     "draft": {
       "label": "Kreirajte i ažurirajte samo nacrte postova"
     },
-    "label": "Dozvole za objavljivanje",
+    "email": {
+      "label": "Adresa e-pošte"
+    },
+    "follow": {
+      "label": "Upravljajte sledećom listom"
+    },
+    "label": "Zahtevane dozvole",
     "media": {
       "label": "Otpremite medije"
     },
+    "mute": {
+      "label": "Isključite korisnike"
+    },
     "none": {
       "label": "Nemate dozvole za objavljivanje"
     },
+    "notSupported": {
+      "hint": "Nije podržano"
+    },
+    "profile": {
+      "label": "Informacije o profilu"
+    },
+    "read": {
+      "label": "Pročitajte kanale"
+    },
     "update": {
       "label": "Ažurirajte postova"
     }
@@ -72,4 +96,4 @@
     },
     "title": "Status servera"
   }
-}
\ No newline at end of file
+}
diff --git a/packages/indiekit/locales/sv.json b/packages/indiekit/locales/sv.json
index 3d28c232..496c570f 100644
--- a/packages/indiekit/locales/sv.json
+++ b/packages/indiekit/locales/sv.json
@@ -9,6 +9,12 @@
     "options": "Alternativ"
   },
   "scope": {
+    "block": {
+      "label": "Blockera användare"
+    },
+    "channels": {
+      "label": "Hantera kanaler"
+    },
     "create": {
       "label": "Skapa inlägg"
     },
@@ -18,13 +24,31 @@
     "draft": {
       "label": "Skapa och uppdatera endast inläggsutkast"
     },
-    "label": "Publiceringsbehörigheter",
+    "email": {
+      "label": "E-postadress"
+    },
+    "follow": {
+      "label": "Hantera följande lista"
+    },
+    "label": "Begärde behörigheter",
     "media": {
       "label": "Ladda upp mediefiler"
     },
+    "mute": {
+      "label": "Stäng av användare"
+    },
     "none": {
       "label": "Du har inga publiceringsbehörigheter"
     },
+    "notSupported": {
+      "hint": "Stöds inte"
+    },
+    "profile": {
+      "label": "Profil information"
+    },
+    "read": {
+      "label": "Läs kanaler"
+    },
     "update": {
       "label": "Uppdatera inlägg"
     }
@@ -72,4 +96,4 @@
     },
     "title": "Serverstatus"
   }
-}
\ No newline at end of file
+}
diff --git a/packages/indiekit/locales/zh-Hans-CN.json b/packages/indiekit/locales/zh-Hans-CN.json
index da7c47fe..c8a715eb 100644
--- a/packages/indiekit/locales/zh-Hans-CN.json
+++ b/packages/indiekit/locales/zh-Hans-CN.json
@@ -9,6 +9,12 @@
     "options": "选项"
   },
   "scope": {
+    "block": {
+      "label": "阻止用户"
+    },
+    "channels": {
+      "label": "管理频道"
+    },
     "create": {
       "label": "创建帖子"
     },
@@ -18,13 +24,31 @@
     "draft": {
       "label": "仅创建和更新草稿帖子"
     },
-    "label": "发布权限",
+    "email": {
+      "label": "电子邮件地址"
+    },
+    "follow": {
+      "label": "管理以下列表"
+    },
+    "label": "请求的权限",
     "media": {
       "label": "上传媒体文件"
     },
+    "mute": {
+      "label": "将用户静音"
+    },
     "none": {
       "label": "你没有任何发布权限"
     },
+    "notSupported": {
+      "hint": "不支持"
+    },
+    "profile": {
+      "label": "个人资料信息"
+    },
+    "read": {
+      "label": "读取频道"
+    },
     "update": {
       "label": "更新帖子"
     }
@@ -72,4 +96,4 @@
     },
     "title": "服务器状态"
   }
-}
\ No newline at end of file
+}


Test patch:
diff --git a/packages/endpoint-auth/test/unit/scope.js b/packages/endpoint-auth/test/unit/scope.js
index 295f9ded..e9f5758d 100644
--- a/packages/endpoint-auth/test/unit/scope.js
+++ b/packages/endpoint-auth/test/unit/scope.js
@@ -10,7 +10,7 @@ describe("endpoint-auth/lib/scope", () => {
     });
     const result = getScopeItems("create update", response);
 
-    assert.equal(result.length, 5);
+    assert.equal(result.length, 2);
     assert.equal(result[0].checked, true);
     assert.equal(result[0].value, "create");
   });