From 748cb9a45502a0aca563b8b9441470bcfbc13a85 Mon Sep 17 00:00:00 2001 From: Pierre Laburthe Date: Thu, 30 Apr 2026 14:55:35 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20v2=20=E2=80=94=20verified-only=20improv?= =?UTF-8?q?ements=20(post-audit)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/authentication/jwt.md | 113 +++-- docs/faq/account-deletion.md | 65 +++ docs/faq/api.md | 179 ++++++++ docs/faq/performance.md | 63 ++- docs/faq/render-api.md | 204 +++++++++ docs/installation/webview.md | 98 +++-- .../current/authentication/jwt.md | 159 ++++--- .../current/faq/account-deletion.md | 65 +++ .../current/faq/api.md | 179 ++++++++ .../current/faq/performance.md | 66 ++- .../current/faq/render-api.md | 204 +++++++++ .../current/installation/webview.md | 122 ++++-- sidebars.json | 19 +- src/css/custom.css | 400 +++++++++++++++++- 14 files changed, 1715 insertions(+), 221 deletions(-) create mode 100644 docs/faq/account-deletion.md create mode 100644 docs/faq/api.md create mode 100644 docs/faq/render-api.md create mode 100644 i18n/en/docusaurus-plugin-content-docs/current/faq/account-deletion.md create mode 100644 i18n/en/docusaurus-plugin-content-docs/current/faq/api.md create mode 100644 i18n/en/docusaurus-plugin-content-docs/current/faq/render-api.md diff --git a/docs/authentication/jwt.md b/docs/authentication/jwt.md index 4374d4d..a098f73 100644 --- a/docs/authentication/jwt.md +++ b/docs/authentication/jwt.md @@ -1,31 +1,33 @@ --- id: jwt title: JWT -description: Authentification via le jeton JWT +description: Authentification via le jeton JWT — génération, transmission, cycle de vie de la session. --- -Ce mode d'authentification permet de connecter automatiquement les utilisateurs à Logora une fois qu'il sont authentifiés à travers votre système de connexion. Cette méthode utilise un jeton JWT (JSON Web Token) signé (JWS) ou chiffré (JWE) pour transmettre les données de l'utilisateur à Logora. +Ce mode d'authentification permet de connecter automatiquement les utilisateurs à Logora une fois qu'ils sont authentifiés à travers votre système de connexion. Cette méthode utilise un jeton **JWT** (JSON Web Token) signé (JWS) ou chiffré (JWE) pour transmettre les données de l'utilisateur à Logora. -### Avant de commencer +## Avant de commencer -- Rendez-vous sur votre [Espace d'administration](https://admin.logora.fr) onglet *Configuration > Authentification* pour choisir le mode d'authentification `JWT`. -- Munissez-vous de votre clé secrète d'API. Cette clé secrète vous servira à créer le jeton JWT. Elle doit rester confidentielle. +- Rendez-vous sur votre [Espace d'administration](https://admin.logora.fr) onglet *Configuration > Authentification* pour choisir le mode d'authentification `JWT`. +- Munissez-vous de votre clé secrète d'API. Cette clé secrète vous servira à créer le jeton JWT. Elle doit rester confidentielle. -### Processus d'authentification +## Processus d'authentification -1. Lorsque l'utilisateur se connecte sur votre site web, vous devez créer le jeton JWT contenant les informations de l'utilisateur. Il sera transmis à Logora. +1. Lorsque l'utilisateur se connecte sur votre site web, vous devez créer le jeton JWT contenant les informations de l'utilisateur. Il sera transmis à Logora. 2. Lorsque l'utilisateur se rend sur une page où est inséré le code Logora, le jeton JWT est inséré dans les variables de configuration Javascript, via le paramètre `remote_auth`. 3. L'application Logora détecte le jeton JWT, le décode, le vérifie et inscrit ou connecte l'utilisateur. -### Mise en place +## Mise en place -> ATTENTION : le jeton JWT transmis à Logora doit toujours être mis à jour selon l'état de l'utilisateur, qu'il soit connecté ou non. Si les pages de votre site web sont derrière un cache, notamment les pages qui contiennent la synthèse du débat, il est possible que le jeton JWT ne soit pas mis à jour. Si la mise en cache gêne la création du jeton JWT, utilisez une autre méthode d'authentification. +:::warning Pages mises en cache +Le jeton JWT transmis à Logora doit toujours être mis à jour selon l'état de l'utilisateur, qu'il soit connecté ou non. Si les pages de votre site web sont derrière un cache, notamment les pages qui contiennent la synthèse du débat, il est possible que le jeton JWT ne soit pas mis à jour. Si la mise en cache gêne la création du jeton JWT, utilisez une autre méthode d'authentification. +::: ### 1. Génération du jeton JWT Grâce à la [sérialisation JSON Web Token](https://jwt.io/), les éditeurs peuvent transmettre les données utilisateurs existantes pour fournir aux utilisateurs une session authentifiée transparente sur Logora. Le jeton JWT doit être généré sur vos serveurs puis transmis à Logora via les variables de configuration javascript. Le jeton peut être signé ou chiffré, en fonction du degré de confidentialité que vous souhaitez obtenir pour les informations utilisateur. -##### Création du corps du jeton +#### Création du corps du jeton Le corps du jeton contient les informations de l'utilisateur sous format JSON : @@ -41,33 +43,38 @@ Le corps du jeton contient les informations de l'utilisateur sous format JSON : ``` Il doit inclure les attributs suivants, sensibles à la casse : + - `uid` : identifiant unique associé à l'utilisateur dans votre base de données. - `first_name` : prénom de l'utilisateur, ou nom d'utilisateur si `last_name` est vide. - `last_name` (optionnel) : nom de famille de l'utilisateur. - `email` : l'adresse email enregistrée pour ce compte. - `image_url` (optionnel) : lien vers l'avatar de l'utilisateur. -- `iat` : date de génération du jeton -- `exp` (optionnel) : date d'expiration du jeton. Si présent, la session ne démarrera pas si le jeton est expiré. +- `iat` : date de génération du jeton. +- `exp` (optionnel) : date d'expiration du jeton. Si présent, **la session ne démarrera pas si le jeton est expiré**. Le nom des champs est personnalisable sur l'espace d'administration si vous avez un format différent. Vous pouvez maintenant créer le jeton, soit en format JWS, ou soit sous format JWE. Si vous n'êtes pas sûr de quelle solution choisir, choisissez la version signée qui est la plus utilisée et la plus simple à mettre en place. -##### JWT signé (JWS) +#### JWT signé (JWS) -> **Important** : Si votre clé secrète est encodée en Base64, activez l'option correspondante dans votre espace d'administration. +:::note Clé secrète encodée en Base64 +Si votre clé secrète est encodée en Base64, activez l'option correspondante dans votre espace d'administration. +::: Le jeton est composée de trois parties, l'en-tête, le corps (payload) que vous avez généré précédemment et la signature. -En-tête du jeton -``` -{ - "alg": "HS256", - "typ": "JWT" +En-tête du jeton : + +```json +{ + "alg": "HS256", + "typ": "JWT" } ``` -Signature +Signature : + ``` HMACSHA256( base64UrlEncode(header) + "." + @@ -76,11 +83,12 @@ HMACSHA256( ) ``` -Exemple en pseudo-code -``` -header = { - "alg": "HS256", - "typ": "JWT" +Exemple en pseudo-code : + +```javascript +header = { + "alg": "HS256", + "typ": "JWT" } payload = { uid: "123abc", @@ -97,47 +105,62 @@ signature = HMACSHA256( // Variable transmise à Logora jetonJWT = base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + signature -=> "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxMjNhYmMiLCJmaXJzdF9uYW1lIjoiSmVhbiIsImxhc3RfbmFtZSI6IkR1cG9udCIsImVtYWlsIjoiamVhbmR1cG9udEBleGVtcGxlLmNvbSIsImlhdCI6MTUxNjIzOTAyMn0.ITnJo8VwbP4PkVTANSt651C0olsrdRNCNmvTHkanuYk" ``` -Avant de passer à la deuxième étape, vérifiez le bon fonctionnement du jeton sur le site web : https://jwt.io/ -##### JWT chiffré (JWE) +Avant de passer à la deuxième étape, vérifiez le bon fonctionnement du jeton sur le site web : [https://jwt.io/](https://jwt.io/). -Pour utiliser ce type de jeton, choisissez l'option "JWE" dans les paramètres de l'espace d'administration. -Le chiffrement du jeton permet de ne pas divulguer les informations utilisateur. Nous ne supportons que les clés de type RSA (RSA-OAEP et RSA1_5). +#### JWT chiffré (JWE) -Pour générer le jeton, nous vous proposons de lire cet article qui explique le processus : https://dzone.com/articles/using-json-web-encryption-jwe +Pour utiliser ce type de jeton, choisissez l'option « JWE » dans les paramètres de l'espace d'administration. Le chiffrement du jeton permet de ne pas divulguer les informations utilisateur. Nous ne supportons que les clés de type RSA (RSA-OAEP et RSA1_5). -Voici un exemple de jeton généré avec le corps généré précédemment, et qui sera transmis à Logora : -``` -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.FuNsYUYJzh294MQZ_71zOxBLiiOkU8UKF4b-wwhCKNKCm1452jnyxzljNTCkTGVhZui6CnBctUByqdvVugMzIlWxNA4hSXWvQUKxm-QJlJyqbdeL6URM2mxeqBxk3iEA7TIbLCd30pnFo8KkSbbHmkDVrVElJ403t0bNKPlvJkjU_Dc71tP3Zun-Nc_3PK9azldEZ7IEPYvd--leYPBBTThNZ--SHSWx_C8rF5a3PHaniVttguHX4EJ39V36xz_7FWFXh4ZNCCFp_kqa05_ixD0EEH11kpxdOv-wm_MgOyt9XODoIWqZUrcDywCkhNy_gIHP8LHbHFhyHR3o8qQvhQ.s_bngM9ad5tmrkQH.GJUshscvO9ZtspkyH-emLbbgyczh_uzFTbv_QEWM3iryARl0UrvYzaBjyBIr3o16bw4PfUCK-4TXTRzV4C56s63BNIwL7fY0AQXrBfRifg8AtaIg0NJyJXbnUzqB7Gx23KruL9g.zSNCxobkIFdAY82DRf1Qdw -``` +Pour générer le jeton, nous vous proposons de lire cet article qui explique le processus : [https://dzone.com/articles/using-json-web-encryption-jwe](https://dzone.com/articles/using-json-web-encryption-jwe). -Avant de passer à la deuxième étape, vérifiez le bon fonctionnement du jeton sur le site web : https://dinochiesa.github.io/jwt/ +Vérifiez le bon fonctionnement du jeton sur : [https://dinochiesa.github.io/jwt/](https://dinochiesa.github.io/jwt/). ### 2. Transmission du jeton à Logora -Une fois que le message a été généré, il doit être transmis via la variable de configuration Javascript, `remote_auth`, dans le code de l'espace de débat. +Une fois que le message a été généré, il doit être transmis via la variable de configuration Javascript, `remote_auth`, dans le code de l'espace de débat : ```javascript var logora_config = { - remote_auth: jeton_JWT + remote_auth: jeton_JWT } ``` -Il faut également remplir l'interface sur l'espace d'administration : - -![Admin JWT](/img/jwtadmin.png) +Il faut également remplir l'interface sur l'espace d'administration. ### 3. Déconnexion de l'utilisateur Pour déconnecter l'utilisateur, retirez le paramètre `remote_auth` ou transmettez une chaîne de caractères vide. Si le paramètre est vide, Logora considère que l'utilisateur est déconnecté. -### 4. Redirection vers l'espace de débat après connexion de l'utilisateur +Pour une déconnexion **côté serveur en temps réel**, utilisez le [Backchannel Logout](/authentication/backchannel-logout). + +### 4. Redirection vers l'espace de débat après connexion + +Lorsqu'un utilisateur non connecté veut effectuer une action sur l'espace de débat, il est redirigé vers votre page de connexion ou d'inscription. Lors de l'insertion de l'espace de débat et de la synthèse, vous devez définir les URLs de connexion et d'inscription, respectivement via les variables `auth.login_url` et `auth.registration_url`. + +Lors de la redirection, un paramètre de requête `logora_redirect` est transmis, contenant l'URL de la page avant redirection. Utilisez ce paramètre pour rediriger l'utilisateur après sa connexion ou son inscription. Le nom du paramètre transmis peut être modifié, il peut être par exemple défini à `redirect_to`. + +Ces paramètres peuvent être changés dans l'espace d'administration, dans l'onglet *Configuration > Authentification*. + +--- + +## Cycle de vie de la session + +### Durée d'une session Logora + +Une fois le JWT validé, Logora ouvre **sa propre session** indépendamment de la vôtre. Cette session expire **2 heures après** sa création par défaut. + +Si vous souhaitez forcer une déconnexion avant cette échéance, utilisez le [Backchannel Logout](/authentication/backchannel-logout). -Lorsqu'un utilisateur non connecté veut effectuer une action sur l'espace de débat, il est redirigé vers votre page de connexion ou d'inscription. Lors de l'insertion de l'espace de débat et de la synthèse, vous devez définir les URLs de connexion et d'inscription, respectivement via les variables auth.login_url et auth.registration_url. +### Validation du claim `exp` -Lors de la redirection, un paramètre de requête logora_redirect est transmis, contenant l'URL de la page avant redirection. Utilisez ce paramètre pour rediriger l'utilisateur après sa connexion ou son inscription. Le nom du paramètre transmis peut être modifié, il peut être par exemple défini à redirect_to. +Si le claim `exp` est présent dans le JWT, Logora vérifie qu'il n'est pas expiré. Un jeton expiré ne démarrera pas de session. -Ces paramètres peuvent être changés dans l'espace d'administration, dans l'onglet *Configuration > Authentification* +:::tip Bonne pratique pour limiter la réutilisation +Si vous craignez qu'un token soit rejoué : +1. **Toujours inclure un claim `exp` court** dans le JWT +2. **Mettre à jour le `remote_auth` à chaque rendu de page** (pas de cache HTML qui fige le token) +3. **Appeler le backchannel logout côté serveur** lors de la déconnexion utilisateur sur votre site +::: diff --git a/docs/faq/account-deletion.md b/docs/faq/account-deletion.md new file mode 100644 index 0000000..9ec6c8e --- /dev/null +++ b/docs/faq/account-deletion.md @@ -0,0 +1,65 @@ +--- +id: account-deletion +title: Suppression et anonymisation d'un utilisateur +description: Comment anonymiser ou supprimer un utilisateur via l'API Logora, et comment notifier Logora d'une suppression côté SSO. +sidebar_label: Suppression d'utilisateur +--- + +L'API Logora propose deux opérations pour gérer la suppression d'un utilisateur, selon votre besoin : + +| Opération | Effet sur les contributions | Endpoint | +|---|---|---| +| **Anonymiser** | ✅ Conservées (auteur masqué) | `POST /api/v1/users/{user_uid}/anonymize` | +| **Supprimer** | ❌ Supprimées | `DELETE /api/v1/users/{user_uid}` | + +Choisissez selon le contexte : + +- **Demande RGPD d'effacement** → anonymisation suffit dans la plupart des cas (équilibre entre droit à l'oubli et préservation du contenu public). +- **Suppression définitive** (compte test, doublon, fraude avérée) → suppression complète. + +## Anonymiser un utilisateur + +```http +POST https://app.logora.fr/api/v1/users/{user_uid}/anonymize +Authorization: Bearer YOUR_TOKEN +``` + +Le `user_uid` est l'identifiant unique que **vous** transmettez à Logora via le SSO (champ `uid` du JWT). + +Réponse `200` en cas de succès. + +## Supprimer un utilisateur + +```http +DELETE https://app.logora.fr/api/v1/users/{user_uid} +Authorization: Bearer YOUR_TOKEN +``` + +:::caution Action irréversible +La suppression efface l'utilisateur **et toutes ses contributions associées**. Cette opération n'est pas réversible. +::: + +## Notifier Logora d'une suppression côté votre système (Backchannel Logout) + +Si votre système d'authentification gère la suppression des comptes côté serveur (ex. RGPD via votre back-office), vous pouvez notifier Logora en temps réel via le [Backchannel Logout](/authentication/backchannel-logout). + +L'endpoint accepte un JWT signé contenant les informations de la suppression. + +```http +POST https://app.logora.fr/auth/logout/{shortname} +Content-Type: application/x-www-form-urlencoded + +logout_token=YOUR_SIGNED_JWT +``` + +Voir la page dédiée pour le détail des claims attendus. + +## Test via Swagger + +Les deux endpoints `anonymize` et `delete` sont testables directement depuis le **[Swagger interactif](https://app.logora.fr/docs)**, section *Users*. + +## Voir aussi + +- [API publique Logora](/faq/api) +- [Backchannel Logout](/authentication/backchannel-logout) +- [Gestion des utilisateurs (FAQ)](/faq/data) — vue d'ensemble RGPD diff --git a/docs/faq/api.md b/docs/faq/api.md new file mode 100644 index 0000000..60fca6a --- /dev/null +++ b/docs/faq/api.md @@ -0,0 +1,179 @@ +--- +id: api +title: Utiliser l'API Logora +description: Récupérer débats, messages, utilisateurs et statistiques via l'API REST Logora. +sidebar_label: API publique +--- + +L'API Logora expose 51 endpoints REST permettant d'accéder programmatiquement à vos débats, messages, utilisateurs, votes, statistiques, etc. + +La documentation interactive complète (Swagger UI) est disponible ici : **[https://app.logora.fr/docs](https://app.logora.fr/docs)**. + +## Authentification + +L'API supporte deux schémas d'authentification (source : `securitySchemes` dans la spec OpenAPI) : + +| Schéma | Type | Transport | +|---|---|---| +| `bearer_auth` | HTTP Bearer | Header `Authorization: Bearer ...` | +| `api_key` | API key | **Paramètre de query string** : `?api_key=...` | + +:::caution Le paramètre `api_key` est en query string +Contrairement à beaucoup d'API, `api_key` n'est **pas** un header — c'est un paramètre d'URL. Cela signifie qu'il peut apparaître dans vos logs serveur et HTTP referer. Pour les usages serveur-à-serveur, préférez le bearer token OAuth (voir ci-dessous). +::: + +### Récupérer un access token OAuth + +```bash +curl -d grant_type=client_credentials \ + -d client_id=YOUR_API_KEY \ + -d client_secret=YOUR_CLIENT_SECRET \ + -d scope=public \ + https://app.logora.fr/oauth/token +``` + +Le scope `public` est celui mentionné dans la documentation officielle de l'API. + +Une fois le token obtenu, ajoutez-le à vos requêtes : + +```bash +curl -H "Authorization: Bearer YOUR_TOKEN" \ + "https://app.logora.fr/api/v1/groups" +``` + +## Conventions communes + +### Pagination + +Les endpoints de liste renvoient les en-têtes HTTP suivants : + +| Header | Description | +|---|---| +| `total` | Nombre total d'éléments | +| `total-pages` | Nombre total de pages | +| `current-page` | Page courante | +| `next-page` | Numéro de la page suivante | +| `page-items` | Éléments par page | +| `link` | Liens RFC 5988 vers les pages adjacentes | + +Paramètres de query string standards sur les endpoints de liste : + +- `page` : numéro de page (défaut 1) +- `per_page` : taille de page +- `cursor_pagination` (booléen) : pour basculer en pagination par curseur +- `countless` (booléen) : ne pas calculer le total (plus rapide) +- `sort` : tri (string, dépend de l'endpoint) + +### Filtres temporels + +Les endpoints de liste supportent les filtres temporels suivants (dates ISO 8601) : + +``` +?created_at.gte=2026-01-01T00:00:00Z +?created_at.lte=2026-01-31T23:59:59Z +``` + +## Endpoints principaux + +### Lister les débats + +```http +GET /api/v1/groups +Authorization: Bearer YOUR_TOKEN +``` + +Paramètres : `page`, `per_page`, `countless`, `sort`, `cursor_pagination`. + +### Lister les messages (arguments et commentaires) + +```http +GET /api/v1/messages?group_id=123 +Authorization: Bearer YOUR_TOKEN +``` + +:::note Vocabulaire +Dans la nomenclature de l'API, les **arguments d'un débat** et les **commentaires** sont tous deux des `messages`. Le tag OpenAPI « Messages » est explicitement décrit comme *« Debate arguments and comments »*. Pour récupérer les arguments d'un débat, filtrez par `group_id`. +::: + +Filtres disponibles : `page`, `per_page`, `sort`, `created_at.gte`/`.lte`, `is_edited`, `status`, `is_reply`, `moderation_score`, `score`, `user_id`, `group_id`, `position_id`, `language`, `is_deleted`, `is_selected`. + +### Récupérer un utilisateur + +```http +GET /api/v1/users/{user_hash_id} +Authorization: Bearer YOUR_TOKEN +``` + +:::warning Trois identifiants utilisateur différents +Selon l'endpoint, l'API utilise trois identifiants différents : + +| Identifiant | Endpoints | +|---|---| +| `user_uid` (UUID que vous fournissez via SSO) | `POST /users/{user_uid}/anonymize`, `DELETE /users/{user_uid}` | +| `user_slug` | `PATCH /users/{user_slug}`, `/users/{user_slug}/messages`, `/users/{user_slug}/badges`, etc. | +| `user_hash_id` | `GET /users/{user_hash_id}` (show) | + +Référez-vous toujours au paramètre attendu par chaque endpoint dans le [Swagger](https://app.logora.fr/docs). +::: + +### Anonymiser un utilisateur + +```http +POST /api/v1/users/{user_uid}/anonymize +Authorization: Bearer YOUR_TOKEN +``` + +Anonymise toutes les données personnelles tout en conservant les contributions. + +### Supprimer un utilisateur + +```http +DELETE /api/v1/users/{user_uid} +Authorization: Bearer YOUR_TOKEN +``` + +### Statistiques + +```http +GET /api/v1/stats/{resource}?filter=day&from_date=2026-01-01&to_date=2026-01-31 +Authorization: Bearer YOUR_TOKEN +``` + +Le segment `{resource}` doit valoir : `users`, `groups`, `consultations`, `messages`, `proposals`, ou `votes`. + +| Paramètre | Description | +|---|---| +| `filter` | Dimension d'agrégation. Défaut `day`. Exemples : `day`, `week`, `month`. | +| `from_date` | Date de début (objets créés après cette date). | +| `to_date` | Date de fin. | + +Réponse type : + +```json +[ + { "dimension": "2026-04-30", "value": 3 }, + { "dimension": "2026-04-29", "value": 7 } +] +``` + +:::tip Granularité quotidienne +Pour des statistiques quotidiennes (et non un total agrégé), passez `filter=day`. +::: + +## Modération via API + +L'admin Logora gère la modération en interne, mais si vous avez votre propre file de modération externe vous pouvez aussi consommer ces endpoints : + +```http +GET /api/v1/moderation_entries +PATCH /api/v1/moderation_entries/{id} +GET /api/v1/moderation_entries/lock +``` + +Le PATCH accepte un body avec `status`, `moderation_reason`, `is_moderated`. + +## Voir aussi + +- **[Swagger interactif](https://app.logora.fr/docs)** — la liste complète des 51 endpoints, avec « Try it out » +- [API de pré-rendu (`render.logora.fr`)](/faq/render-api) — pour récupérer du HTML pré-rendu pour newsletters et homepages +- [Suppression et anonymisation d'utilisateurs](/faq/account-deletion) diff --git a/docs/faq/performance.md b/docs/faq/performance.md index 68b0f46..e69aed6 100644 --- a/docs/faq/performance.md +++ b/docs/faq/performance.md @@ -1,25 +1,66 @@ --- id: performance title: Performance -description: Nous optimisons la performance de nos scripts pour un meilleur référencement. +description: Logora optimise ses scripts pour minimiser leur impact sur le chargement de vos pages. --- -Logora met tout en oeuvre pour que le code inséré aie le moins d'impact sur le chargement de vos pages. Voici quelques détails sur le fonctionnement de Logora pour mieux évaluer la performance des scripts insérés. +Logora met tout en œuvre pour que le code inséré ait le moins d'impact sur le chargement de vos pages. Voici quelques détails sur le fonctionnement de Logora pour mieux évaluer la performance des scripts insérés. +## 1) Synthèse en pied d'article -### 1) Synthèse en pied d'article +Le script de la synthèse est servi depuis : +``` +https://api.logora.fr/synthese.js +``` Le code de la synthèse inséré dans vos articles procède en quatre étapes : -1. Téléchargement du script **embed.js**. Ce fichier, servi depuis le CDN DigitalOcean, a une taille de **8 Ko**. Il permet de lancer les fonctionnalités de Logora et gère les appels vers notre API. -2. Appel vers l'API Logora pour vérifier si un débat correspond à la page en question. Cet appel renvoie le code HTML de la synthèse s'il y a un débat associé. La réponse a une taille de **9 Ko**, et le temps de réponse médian est de **10ms**. Nos serveurs sont situés à Paris. -3. Insertion du code dans la page. Le code est pré-rendu par le serveur, il peut donc être inséré directement sans traitement supplémentaire. -4. (Optionnel) Pour le premier appel de la page seulement, le script envoie les métadonnées de la page (titre, étiquettes, description) à nos serveurs pour associer ensuite plus facilement des débats aux articles. -> Si vous avez des contraintes élevées en terme de performance, utilisez l'[insertion côté serveur](../../installation/server-side-sdk) +1. **Téléchargement du script** `synthese.js` +2. **Appel API Logora** pour vérifier si un débat correspond à la page en question. Cet appel renvoie le code HTML de la synthèse s'il y a un débat associé. Le code est pré-rendu par le serveur. +3. **Insertion du code dans la page** sans traitement supplémentaire côté client. +4. **(Optionnel)** Pour le premier appel de la page seulement, le script envoie les métadonnées de la page (titre, étiquettes, description) à nos serveurs pour faciliter l'association article/débat. -### 2) Espace de débat +:::tip Contraintes de performance élevées ? +Utilisez l'[insertion côté serveur](/installation/server-side-sdk) pour récupérer le HTML directement dans votre template, sans appel client. Voir aussi [API de pré-rendu](/faq/render-api). +::: -Le code de l'espace de débat procède de la même manière que celui de la synthèse, mais télécharge plus de scripts et fait plus d'appels à notre API en fonction des actions et de la navigation de l'utilisateur. Le fichier initial **debat.js** a une taille de **60 Ko**. +## 2) Espace de débat -Notre API a un temps de réponse médian de **15ms** (95è centile 50ms) et compte plusieurs millions de requêtes par jour. +Le script de l'espace de débat est servi depuis : + +``` +https://api.logora.fr/debat.js +``` + +Le code procède de la même manière que celui de la synthèse, mais télécharge plus de scripts et fait plus d'appels à notre API en fonction des actions et de la navigation de l'utilisateur. + +## 3) Précharger les domaines Logora + +Pour accélérer le premier chargement, vous pouvez précharger les domaines Logora dans le `` de votre page : + +```html + + + + +``` + +Ces directives signalent au navigateur d'établir les connexions TCP/TLS en parallèle du parsing HTML. + +## 4) Mesurer l'impact réel + +Pour mesurer l'impact de Logora sur vos pages : + +1. **Pagespeed Insights** : [pagespeed.web.dev](https://pagespeed.web.dev/) — comparez vos pages avec et sans Logora +2. **Chrome DevTools > Performance** : enregistrement de session pour voir le coût en CPU +3. **Real User Monitoring** : si vous utilisez Datadog, New Relic ou Contentsquare, vous pouvez tagger les pages Logora pour suivre leur impact dans le temps + +--- + +## Voir aussi + +- [Installation côté serveur](/installation/server-side-sdk) (sans appel client) +- [Module de commentaires](/installation/module-commentaires) +- [Installation Javascript](/installation/javascript-sdk) +- [API de pré-rendu (`render.logora.fr`)](/faq/render-api) diff --git a/docs/faq/render-api.md b/docs/faq/render-api.md new file mode 100644 index 0000000..65093f0 --- /dev/null +++ b/docs/faq/render-api.md @@ -0,0 +1,204 @@ +--- +id: render-api +title: API de pré-rendu (render.logora.fr) +description: Récupérez le HTML pré-rendu des débats, commentaires et widgets Logora pour les insérer dans une newsletter, une page d'accueil custom ou un CMS. +sidebar_label: API de pré-rendu +--- + +`render.logora.fr` est l'**API de pré-rendu** de Logora. Elle vous permet de récupérer directement le HTML d'un widget (débat, commentaires, vote, consultation…) sans charger le script JavaScript côté client. + +:::tip Cas d'usage typiques +- Insérer les **commentaires d'un article** dans une newsletter +- Construire une **page d'accueil custom** mettant en avant les débats les plus actifs +- Pré-rendre côté serveur pour le **SEO** (le HTML sert directement les crawlers) +- Brancher un **CMS tiers** (Livingdocs, Drupal, etc.) qui consomme du HTML +::: + +Documentation interactive complète : **[https://render.logora.fr/docs](https://render.logora.fr/docs)**. + +## Authentification + +Aucune. Les endpoints render acceptent les requêtes anonymes — le contrôle d'accès se fait via le `shortname` de votre application. + +## Paramètres communs + +Tous les endpoints partagent ces paramètres en query string : + +| Paramètre | Type | Requis | Description | +|---|---|---|---| +| `shortname` | string | ✅ | Le shortname de votre application (visible dans l'admin Logora) | +| `language` | string | optionnel | Langue d'affichage : `fr`, `en`, `es`, ou `de`. Défaut `fr`. | +| `noHtml` | boolean | optionnel | Si `true`, ne renvoie que les métadonnées (sans le HTML compilé) | + +## Endpoints + +### `POST /synthesis` — synthèse d'un débat + +Renvoie le HTML de la synthèse d'un débat lié à une page de votre site. + +| Paramètre | Type | Requis | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `uid` | string | ✅ | Identifiant de la page (le `uid` de votre source) | +| `device` | string | optionnel | `mobile`, `tablet` ou `desktop` (pour adapter le responsive) | +| `language` | string | optionnel | | +| `noHtml` | boolean | optionnel | | + +**Réponse** : + +```json +{ + "success": true, + "debate": { + "id": 123, + "slug": "my-debate", + "name": "My debate ?", + "direct_url": "https://example.com/debate/my-debate", + "type": "Group" + }, + "content": "
...
" +} +``` + +**Exemple réel (production)** : + +``` +POST https://render.logora.fr/synthesis?shortname=milenio-4e7a57&uid=2050434 +``` + +### `POST /embed/comments` — module de commentaires + +Renvoie le HTML du module de commentaires associé à une source. + +| Paramètre | Type | Requis | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `uid` | string | ✅ | Identifiant unique de la source | +| `language` | string | optionnel | | +| `noHtml` | boolean | optionnel | | + +**Exemple réel (production)** : + +``` +POST https://render.logora.fr/embed/comments?shortname=krone-f8d02e&uid=3930450&device=tablet +``` + +### `GET /app` — page complète d'espace de débat + +Renvoie le HTML d'une page complète d'espace de débat (ex. landing page débat). + +| Paramètre | Type | Requis | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `url` | string | ✅ | URL de la page à rendre | + +### `POST /widget` — widget de débat (par `uid`) + +| Paramètre | Type | Requis | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `uid` | string | optionnel | | +| `language` | string | optionnel | | +| `noHtml` | boolean | optionnel | | + +### `POST /embed/argument` — widget d'un argument particulier + +| Paramètre | Type | Requis | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `id` | string | ✅ | Identifiant de l'argument | +| `language` | string | optionnel | | +| `noHtml` | boolean | optionnel | | + +### `POST /embed/group` — widget d'un débat (par `id`) + +| Paramètre | Type | Requis | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `id` | string | ✅ | Identifiant du débat (group) | +| `language` | string | optionnel | | +| `noHtml` | boolean | optionnel | | + +### `POST /embed/proposal` — widget d'une proposition + +| Paramètre | Type | Requis | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `id` | string | ✅ | Identifiant de la proposition | +| `language` | string | optionnel | | +| `noHtml` | boolean | optionnel | | + +### `POST /embed/consultation` — widget d'une consultation + +| Paramètre | Type | Requis | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `id` | string | ✅ | Slug de la consultation | +| `language` | string | optionnel | | +| `noHtml` | boolean | optionnel | | + +### `POST /embed/vote` — widget de vote + +| Paramètre | Type | Requis | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `id` | string | ✅ | Identifiant du vote | +| `language` | string | optionnel | | +| `noHtml` | boolean | optionnel | | + +## Réponse type + +La plupart des endpoints `/embed/*` renvoient : + +```json +{ + "success": true, + "resource": { "id": 1234 }, + "html": "
...
" +} +``` + +`/synthesis` et `/widget` renvoient un payload légèrement différent (avec une clé `debate` plutôt que `resource`, et `content` plutôt que `html`). + +## Erreurs + +En cas d'échec, la réponse est de la forme : + +```json +{ + "success": false, + "error": "Data fetching error" +} +``` + +## Exemple complet : top débats dans une newsletter + +```javascript +// Côté serveur de votre newsletter (Node.js par exemple) + +// 1. Récupérer la liste des débats récents via l'API publique +const groupsRes = await fetch( + 'https://app.logora.fr/api/v1/groups?per_page=3', + { headers: { 'Authorization': `Bearer ${accessToken}` } } +); +const { data: debates } = await groupsRes.json(); + +// 2. Pour chaque débat, récupérer la synthèse pré-rendue +const blocks = await Promise.all(debates.map(async (d) => { + const res = await fetch( + `https://render.logora.fr/synthesis?shortname=YOUR_APP&uid=${d.uid}`, + { method: 'POST' } + ); + const { content } = await res.json(); + return content; +})); + +// 3. Insérer dans votre template d'email +const newsletterHtml = template.replace('{{debates}}', blocks.join('')); +``` + +## Voir aussi + +- **[Swagger interactif du Render API](https://render.logora.fr/docs)** +- [API publique Logora](/faq/api) — pour les endpoints REST classiques +- [Installation côté serveur](/installation/server-side-sdk) — alternative pour intégrer le HTML dans vos pages d'articles diff --git a/docs/installation/webview.md b/docs/installation/webview.md index 9436a93..f578071 100644 --- a/docs/installation/webview.md +++ b/docs/installation/webview.md @@ -1,27 +1,26 @@ --- id: webview title: Webview +description: Intégrer Logora en WebView dans une application mobile (iOS / Android / React Native) avec SSO. --- -# Intégration en WebView (avec SSO) - -## Introduction - L'intégration de Logora en WebView permet d'afficher l'espace de débat directement au sein d'une application mobile ou d'un site en mode WebView. Cette approche garantit une expérience utilisateur fluide tout en conservant les fonctionnalités interactives de Logora. Deux applications ont déjà intégré Logora avec succès : **Der SPIEGEL** et **Suedkurier**. Vous pouvez voir ces intégrations en action aux liens suivants : - [Der SPIEGEL](https://www.loom.com/share/725de75c09d64911ad42fdff7acf07e7?sid=c5d01191-5783-4980-be81-f1a21e162e87) - [Suedkurier](https://www.loom.com/share/b3eabe7ab0d1417f8cbbfd29735c2adf?sid=356bc7c1-559e-4f2e-bece-7cecc328cb6e) -Pour une authentification transparente des utilisateurs, Logora supporte l'Authentification Unique (SSO) via l'injection d'un jeton dans l'objet `logora_config` sous le paramètre `remote_auth`. **L'intégration SSO de Logora est uniquement compatible avec la méthode JWT.** +:::warning SSO compatible JWT uniquement +L'intégration SSO de Logora en WebView est **uniquement compatible avec la méthode JWT**. Voir [Authentification JWT](/authentication/jwt). +::: ## 1. Installation de Logora en WebView -L'installation de Logora en WebView suit la même procédure que l'installation classique en insérant le code JavaScript standard. Voici les étapes à suivre : +L'installation de Logora en WebView suit la même procédure que l'installation classique en insérant le code JavaScript standard. ### 1.1 Création de la WebView -Dans votre application mobile ou site WebView, créez une vue Web qui charge l'URL de l'espace de débat. Exemple en HTML : +Hébergez sur votre site une page minimale qui charge le widget Logora : ```html @@ -36,7 +35,7 @@ Dans votre application mobile ou site WebView, créez une vue Web qui charge l'U remote_auth: "VOTRE_JETON_JWT" }; - +
@@ -46,7 +45,7 @@ Dans votre application mobile ou site WebView, créez une vue Web qui charge l'U ### 1.2 Chargement dans une WebView mobile -**Exemple en Swift (iOS)** +#### iOS (Swift) ```swift import UIKit @@ -54,13 +53,13 @@ import WebKit class DebateViewController: UIViewController { var webView: WKWebView! - + override func loadView() { webView = WKWebView() webView.configuration.preferences.javaScriptEnabled = true view = webView } - + override func viewDidLoad() { super.viewDidLoad() let url = URL(string: "https://votresite.com/espace-debat")! @@ -69,11 +68,10 @@ class DebateViewController: UIViewController { } ``` -**Exemple en Kotlin (Android)** +#### Android (Kotlin) ```kotlin import android.os.Bundle -import android.webkit.WebSettings import android.webkit.WebView import androidx.appcompat.app.AppCompatActivity @@ -88,15 +86,20 @@ class DebateActivity : AppCompatActivity() { } ``` -## 2. Authentification Unique (SSO) en WebView +#### React Native -Pour que les utilisateurs soient automatiquement authentifiés sur Logora sans ressaisie de leurs identifiants, il est nécessaire d’injecter un jeton dans `remote_auth` au sein de `logora_config`. +```javascript +import { WebView } from 'react-native-webview'; -**L'intégration SSO de Logora est uniquement compatible avec la méthode JWT.** + +``` -Vous pouvez consulter le guide détaillé sur l’authentification dans notre [documentation JWT](../../authentication/jwt). +## 2. Authentification SSO en WebView -Exemple de configuration : +Pour que les utilisateurs soient automatiquement authentifiés sur Logora sans ressaisie de leurs identifiants, il est nécessaire d'injecter un jeton dans `remote_auth` au sein de `logora_config`. ```javascript var logora_config = { @@ -105,6 +108,8 @@ var logora_config = { }; ``` +Voir le guide complet : [Authentification JWT](/authentication/jwt). + ### Déconnexion de l'utilisateur Pour déconnecter un utilisateur, il suffit de retirer la valeur de `remote_auth` ou de transmettre une chaîne vide : @@ -116,20 +121,59 @@ var logora_config = { }; ``` -## 3. Route initiale +## 3. Adapter l'affichage à la WebView via un paramètre d'URL + +Pour différencier le comportement de votre WebView de votre vue web responsive, une convention courante (utilisée par exemple par Cronista) consiste à ajouter un paramètre dans l'URL de la page hôte de la WebView : + +``` +https://votresite.com/espace-debat?outputType=webapp-type +``` + +Logora ne traite pas ce paramètre directement — c'est à votre intégration de le détecter et d'adapter le comportement (CSS custom, interception de liens, etc.). + +### Exemple : intercepter les liens internes pour les renvoyer au routeur natif + +Logora émet un événement DOM `logoraContentLoaded` une fois que le widget est rendu. Vous pouvez l'utiliser pour modifier le comportement des liens : -Pour définir la première page affichée dans la webview, vous pouvez passer en paramètre `initial_path` avec le chemin voulu : ```javascript -var logora_config = { - shortname: "NOM_APPLICATION", - initial_path: "/debat/titre-de-debat" -}; +window.addEventListener("logoraContentLoaded", () => { + const currentUrl = new URL(window.location.href); + if (currentUrl.searchParams.get("outputType") !== "webapp-type") return; + + const root = document.querySelector("#logoraRoot"); + if (!root) return; + + const links = root.querySelectorAll("a[href]"); + links.forEach((link) => { + link.addEventListener("click", (e) => { + e.preventDefault(); + // Renvoyer l'URL au routeur natif + window.ReactNativeWebView?.postMessage(link.href); + // ou pour iOS/Android natif : + // window.webkit?.messageHandlers?.linkHandler?.postMessage(link.href); + }); + }); +}); ``` -L'application s'ouvrira alors directement sur cette page. Cela permet d'avoir un lien direct vers les pages de l'espace de débat. +Côté natif, écoutez les messages et déclenchez votre navigation custom. + +## 4. Persistance de la session + +Si votre app utilise un gestionnaire de session natif (Piano, Auth0, OAuth interne) : +1. Récupérez le token de session natif au moment d'ouvrir la WebView +2. Régénérez un JWT Logora à partir des claims natifs (côté serveur) +3. Passez-le en `remote_auth` comme ci-dessus +:::caution Ne tentez pas de partager les cookies natifs +Le partage de cookies entre l'app native et la WebView est fragile et bloqué par iOS/Android dans la plupart des cas. Passez **toujours** par un JWT régénéré à chaque ouverture de WebView. +::: + +--- -## Conclusion +## Voir aussi -Avec cette intégration, vos utilisateurs peuvent interagir sur Logora sans friction, directement depuis votre application, tout en bénéficiant d'une authentification sécurisée et transparente via JWT. +- [Authentification JWT](/authentication/jwt) +- [Apparence et thème](/configuration/theme) +- [API de pré-rendu](/faq/render-api) — alternative pour récupérer le HTML pré-rendu côté serveur diff --git a/i18n/en/docusaurus-plugin-content-docs/current/authentication/jwt.md b/i18n/en/docusaurus-plugin-content-docs/current/authentication/jwt.md index 92d07a8..c3e46aa 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/authentication/jwt.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/authentication/jwt.md @@ -1,72 +1,80 @@ --- id: jwt title: JWT +description: Authentication via JWT — generation, transmission, session lifecycle. --- -This authentication mode automatically connects users to Logora once they have been authenticated through your login system. This method uses a signed (JWS) or encrypted (JWE) JSON Web Token (JWT) to transmit user data to Logora. +This authentication mode automatically logs users into Logora once they're authenticated through your login system. It uses a **JWT** (JSON Web Token), signed (JWS) or encrypted (JWE), to transmit user data to Logora. -### Before you start +## Before you start -- Go to your [Administration space](https://admin.logora.fr) (*Configuration > Authentication*) to choose the authentication mode `JWT signature`. -- Get your API secret key. This secret key will be used to create the JWT token. It must be kept confidential. +- Go to your [admin space](https://admin.logora.fr), tab *Configuration > Authentication*, to choose the `JWT` authentication mode. +- Have your **API secret key** ready. This secret will be used to create the JWT. It must remain confidential. -### Authentication process +## Authentication flow -1. When the user connects to your website, you must create a JWT token containing the user's information. It will be transmitted to Logora. -2. When the user goes to a page where the Logora code is inserted, the JWT token is inserted in the Javascript configuration variables, via the `remote_auth` parameter. -3. The Logora application detects the JWT token, decodes it, verifies it and signs in or signs up the user. +1. When the user logs in on your site, you generate the JWT containing the user info on your server. It is then transmitted to Logora. +2. When the user reaches a page where Logora code is embedded, the JWT is injected into the JavaScript config via the `remote_auth` parameter. +3. The Logora app detects the token, decodes, verifies, and signs the user in or registers them. -### Set up +## Setup -> WARNING : the JWT token transmitted to Logora must always be updated according to the state of the user, whether they are connected or not. If the pages of your website are behind a cache, especially the pages that contain the debate summary, it is possible that the JWT token is not updated. If caching is interfering with the creation of the JWT token, use another authentication method. +:::warning Cached pages +The JWT must always reflect the user's current state, logged in or not. If your pages are behind a cache (especially pages containing the debate synthesis), the token may not be refreshed. If caching prevents fresh token generation, use a different authentication method. +::: -#### 1. Generation of the JWT token +### 1. Generate the JWT -Using [JSON Web Token serialization](https://jwt.io/), editors can pass on existing user data to provide users with a seamless, authenticated session on Logora. The JWT token must be generated on your servers and then transmitted to Logora via javascript configuration variables. The token can be signed or encrypted, depending on the degree of confidentiality you require for user information. +Through [JSON Web Token serialization](https://jwt.io/), publishers can transmit existing user data to provide a seamless authenticated session on Logora. The token must be generated on **your servers** and transmitted via JavaScript config. It can be signed or encrypted, depending on your confidentiality needs. -##### Creating the token payload +#### Token body -The token payload contains user information in JSON format: +The body contains user info as JSON: ```json { "uid": "12345abc", - "email": "jean@logora.fr", - "first_name": "Jean", - "last_name": "Dupont", + "email": "john@logora.fr", + "first_name": "John", + "last_name": "Doe", "iat": 1755007651, "exp": 1755011251 } ``` -It must include the following case-sensitive attributes: -- `uid`: unique identifier associated with the user in your database. -- `first_name`: user's first name, or username if `last_name` is empty. -- `last_name` (optional): user's surname. -- email`: the email address registered for this account. -- image_url` (optional): link to the user's avatar. -- `iat`: token generation date. -- `exp` (optional) : token expiration date. If present, session will not start if the token is expired +It must include the following fields, case-sensitive: -Field names can be customized in the administration area if you have a different format. +- `uid`: unique user identifier in your database. +- `first_name`: first name (or username if `last_name` is empty). +- `last_name` (optional): last name. +- `email`: registered email address. +- `image_url` (optional): avatar URL. +- `iat`: token issued-at timestamp. +- `exp` (optional): expiration timestamp. If present, **expired tokens won't start a session**. -You can now create the token in either JWS (by default) or JWE format. If you're not sure which solution to choose, choose the signed version, which is the most widely used and easiest to set up. +Field names are customizable in the admin if your system uses a different format. -##### Signed JWT (JWS) +You can now create the token, either as JWS or JWE. If unsure, use the signed version (most common, simplest). -> **Important** : If your secret key is encoded in Base64, enable the corresponding option in your administration interface. +#### Signed JWT (JWS) -The token is made up of three parts: the header, the payload you generated earlier, and the signature. +:::note Base64-encoded secret key +If your secret key is Base64-encoded, enable the corresponding option in your admin. +::: -Token header -``` -{ - "alg": "HS256", - "typ": "JWT +The token has three parts: header, payload, and signature. + +Header: + +```json +{ + "alg": "HS256", + "typ": "JWT" } ``` -Signature +Signature: + ``` HMACSHA256( base64UrlEncode(header) + "." + @@ -75,17 +83,18 @@ HMACSHA256( ) ``` -Pseudo-code example -``` -header = { - "alg": "HS256", - "typ": "JWT" +Pseudo-code example: + +```javascript +header = { + "alg": "HS256", + "typ": "JWT" } payload = { uid: "123abc", - first_name: "Jean", - last_name: "Dupont", - email: "jeandupont@exemple.com", + first_name: "John", + last_name: "Doe", + email: "johndoe@example.com", iat: 1516239022 } signature = HMACSHA256( @@ -95,43 +104,63 @@ signature = HMACSHA256( ) // Variable transmitted to Logora -tokenJWT = base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + signature -=> "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxMjNhYmMiLCJmaXJzdF9uYW1lIjoiSmVhbiIsImxhc3RfbmFtZSI6IkR1cG9udCIsImVtYWlsIjoiamVhbmR1cG9udEBleGVtcGxlmNvbSIsImlhdCI6MTUxNjIzOTAyMn0. ITnJo8VwbP4PkVTANSt651C0olsrdRNCNmvTHkanuYk" +jwtToken = base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + signature ``` -Before proceeding to the second step, check that the token works properly on the website: https://jwt.io/ -##### Encrypted JWT (JWE) +Before moving to step 2, validate the token at [https://jwt.io/](https://jwt.io/). -To use this type of token, select the "JWE" option in the administration settings. -Encrypting the token ensures that user information is not divulged. We only support RSA keys (RSA-OAEP and RSA1_5). +#### Encrypted JWT (JWE) -To generate the token, we suggest you read this article, which explains the process: https://dzone.com/articles/using-json-web-encryption-jwe +To use this token type, choose "JWE" in admin settings. Encryption prevents leaking user info if the token is intercepted. We only support **RSA** keys (RSA-OAEP and RSA1_5). -Here's an example of a token generated with the body generated above, and which will be transmitted to Logora: -``` -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ. FuNsYUYJzh294MQZ_71zOxBLiiOkU8UKF4b-wwhCKNKCm1452jnyxzljNTCkTGVhZui6CnBctUByqdvVugMzIlWxNA4hSXWvQUKxm-QJlJyqbdeL6URM2mxeqBxk3iEA7TIbLCd30pnFo8KkSbbHmkDVrVElJ403t0bNKPlvJkjU_Dc71tP3Zun- Nc_3PK9azldEZ7IEPYvd--leYPBBTThNZ--SHSWx_C8rF5a3PHaniVttguHX4EJ39V36xz_7FWFXh4ZNCCFp_kqa05_ixD0EEH11kpxdOv-wm_MgOyt9XODoIWqZUrcDywCkhNy_gIHP8LHbHFhyHR3o8qQvhQ. s_bngM9ad5tmrkQH.GJUshscvO9ZtspkyH-emLbbgyczh_uzFTbv_QEWM3iryARl0UrvYzaBjyBIr3o16bw4PfUCK-4TXTRzV4C56s63BNIwL7fY0AQXrBfRifg8AtaIg0NJyJXbnUzqB7Gx23KruL9g.zSNCxobkIFdAY82DRf1Qdw -``` +Tutorial: [https://dzone.com/articles/using-json-web-encryption-jwe](https://dzone.com/articles/using-json-web-encryption-jwe). -Before proceeding to the second step, check that the token works properly on the website: https://dinochiesa.github.io/jwt/ +Validate at: [https://dinochiesa.github.io/jwt/](https://dinochiesa.github.io/jwt/). -#### 2. Transmission of the token to Logora +### 2. Transmit the token to Logora -Once the message has been generated, it must be transmitted via the Javascript configuration variable, `remote_auth`, in the debate space code. +Once generated, transmit the token via the `remote_auth` JavaScript variable: ```javascript var logora_config = { - remote_auth: jeton_JWT + remote_auth: jwt_token } ``` -#### 3. Disconnecting the user +Also fill in the admin interface. + +### 3. User logout + +To log the user out, remove the `remote_auth` parameter or pass an empty string. Logora then considers the user logged out. + +For real-time **server-side logout**, use [Backchannel Logout](/authentication/backchannel-logout). + +### 4. Redirect after login + +When an unauthenticated user tries an action on the debate space, they're redirected to your login or signup page. Set these URLs via `auth.login_url` and `auth.registration_url`. + +On redirect, a `logora_redirect` query parameter is passed, containing the URL of the page before redirect. Use it to bring the user back. The parameter name is customizable (e.g. `redirect_to`). + +These settings are in *Admin > Configuration > Authentication*. + +--- + +## Session lifecycle + +### Logora session duration + +Once the JWT is validated, Logora opens **its own session**, independent from yours. By default, this session expires **2 hours** after creation. -To disconnect the user, remove the `remote_auth` parameter or transmit an empty string. If the parameter is empty, Logora considers that the user is disconnected. +To force an earlier logout, use [Backchannel Logout](/authentication/backchannel-logout). -#### 4. Redirection to the debate speace after user log-in +### `exp` claim validation -When a user who is not logged in wants to perform an action in the discussion forum, they are redirected to your login or registration page. When inserting the discussion forum and the overview, you must define the login and registration URLs, via the auth.login_url and auth.registration_url variables respectively. +If the `exp` claim is present in the JWT, Logora checks that it is not expired. An expired token will not start a session. -When redirecting, a logora_redirect request parameter is passed, containing the URL of the page before redirection. Use this parameter to redirect the user after login or registration. The name of the parameter passed can be changed, for example to `redirect_to`. +:::tip Best practice to limit replay +If you're concerned about a token being replayed: -These parameters can be changed in the administration area, in the *Configuration > Authentication* tab. +1. **Always include a short `exp` claim** in the JWT +2. **Refresh the `remote_auth` on every page render** (no HTML cache freezing the token) +3. **Call backchannel logout server-side** when the user logs out on your site +::: diff --git a/i18n/en/docusaurus-plugin-content-docs/current/faq/account-deletion.md b/i18n/en/docusaurus-plugin-content-docs/current/faq/account-deletion.md new file mode 100644 index 0000000..e0e205e --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/current/faq/account-deletion.md @@ -0,0 +1,65 @@ +--- +id: account-deletion +title: Deleting and anonymizing a user +description: How to anonymize or delete a user via the Logora API, and how to notify Logora of an SSO-side deletion. +sidebar_label: User deletion +--- + +The Logora API offers two operations to handle user deletion, depending on your need: + +| Operation | Effect on contributions | Endpoint | +|---|---|---| +| **Anonymize** | ✅ Kept (author hidden) | `POST /api/v1/users/{user_uid}/anonymize` | +| **Delete** | ❌ Removed | `DELETE /api/v1/users/{user_uid}` | + +Choose based on context: + +- **GDPR erasure request** → anonymization is usually enough (balances right to be forgotten with preserving public content). +- **Permanent deletion** (test account, duplicate, confirmed fraud) → full deletion. + +## Anonymize a user + +```http +POST https://app.logora.fr/api/v1/users/{user_uid}/anonymize +Authorization: Bearer YOUR_TOKEN +``` + +The `user_uid` is the unique identifier **you** pass to Logora via SSO (the JWT's `uid` field). + +Returns `200` on success. + +## Delete a user + +```http +DELETE https://app.logora.fr/api/v1/users/{user_uid} +Authorization: Bearer YOUR_TOKEN +``` + +:::caution Irreversible +Deletion removes the user **and all their contributions**. This operation is not reversible. +::: + +## Notify Logora of a deletion on your side (Backchannel Logout) + +If your authentication system handles deletion server-side (e.g. GDPR via your back-office), you can notify Logora in real time via [Backchannel Logout](/authentication/backchannel-logout). + +The endpoint accepts a signed JWT containing the deletion details. + +```http +POST https://app.logora.fr/auth/logout/{shortname} +Content-Type: application/x-www-form-urlencoded + +logout_token=YOUR_SIGNED_JWT +``` + +See the dedicated page for the expected claims. + +## Test via Swagger + +Both `anonymize` and `delete` endpoints are testable directly from the **[interactive Swagger](https://app.logora.fr/docs)**, *Users* section. + +## See also + +- [Public Logora API](/faq/api) +- [Backchannel Logout](/authentication/backchannel-logout) +- [User management (FAQ)](/faq/data) — GDPR overview diff --git a/i18n/en/docusaurus-plugin-content-docs/current/faq/api.md b/i18n/en/docusaurus-plugin-content-docs/current/faq/api.md new file mode 100644 index 0000000..740a3d3 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/current/faq/api.md @@ -0,0 +1,179 @@ +--- +id: api +title: Using the Logora API +description: Fetch debates, messages, users, and statistics through the Logora REST API. +sidebar_label: Public API +--- + +The Logora API exposes 51 REST endpoints to programmatically access your debates, messages, users, votes, statistics, and more. + +The full interactive documentation (Swagger UI) lives here: **[https://app.logora.fr/docs](https://app.logora.fr/docs)**. + +## Authentication + +The API supports two authentication schemes (source: `securitySchemes` in the OpenAPI spec): + +| Scheme | Type | Transport | +|---|---|---| +| `bearer_auth` | HTTP Bearer | `Authorization: Bearer ...` header | +| `api_key` | API key | **Query string parameter**: `?api_key=...` | + +:::caution `api_key` is a query parameter +Unlike many APIs, `api_key` is **not** a header — it's a URL parameter. This means it can show up in your server logs and HTTP referer. For server-to-server use, prefer the OAuth bearer token (see below). +::: + +### Get an OAuth access token + +```bash +curl -d grant_type=client_credentials \ + -d client_id=YOUR_API_KEY \ + -d client_secret=YOUR_CLIENT_SECRET \ + -d scope=public \ + https://app.logora.fr/oauth/token +``` + +The `public` scope is the one mentioned in the official API documentation. + +Once you have the token, add it to your requests: + +```bash +curl -H "Authorization: Bearer YOUR_TOKEN" \ + "https://app.logora.fr/api/v1/groups" +``` + +## Common conventions + +### Pagination + +List endpoints return the following HTTP headers: + +| Header | Description | +|---|---| +| `total` | Total number of items | +| `total-pages` | Total number of pages | +| `current-page` | Current page | +| `next-page` | Next page number | +| `page-items` | Items per page | +| `link` | RFC 5988 links to adjacent pages | + +Standard query parameters on list endpoints: + +- `page` — page number (default 1) +- `per_page` — page size +- `cursor_pagination` (boolean) — switch to cursor-based pagination +- `countless` (boolean) — skip total count (faster) +- `sort` — sort key (string, depends on the endpoint) + +### Date filters + +List endpoints support date filters (ISO 8601): + +``` +?created_at.gte=2026-01-01T00:00:00Z +?created_at.lte=2026-01-31T23:59:59Z +``` + +## Main endpoints + +### List debates + +```http +GET /api/v1/groups +Authorization: Bearer YOUR_TOKEN +``` + +Parameters: `page`, `per_page`, `countless`, `sort`, `cursor_pagination`. + +### List messages (arguments and comments) + +```http +GET /api/v1/messages?group_id=123 +Authorization: Bearer YOUR_TOKEN +``` + +:::note Vocabulary +In the API model, **debate arguments** and **comments** are both `messages`. The OpenAPI tag "Messages" is described as *"Debate arguments and comments"*. To fetch the arguments of a debate, filter by `group_id`. +::: + +Available filters: `page`, `per_page`, `sort`, `created_at.gte`/`.lte`, `is_edited`, `status`, `is_reply`, `moderation_score`, `score`, `user_id`, `group_id`, `position_id`, `language`, `is_deleted`, `is_selected`. + +### Get a user + +```http +GET /api/v1/users/{user_hash_id} +Authorization: Bearer YOUR_TOKEN +``` + +:::warning Three different user identifiers +Depending on the endpoint, the API uses three different user identifiers: + +| Identifier | Endpoints | +|---|---| +| `user_uid` (UUID you provide via SSO) | `POST /users/{user_uid}/anonymize`, `DELETE /users/{user_uid}` | +| `user_slug` | `PATCH /users/{user_slug}`, `/users/{user_slug}/messages`, `/users/{user_slug}/badges`, etc. | +| `user_hash_id` | `GET /users/{user_hash_id}` (show) | + +Always check the parameter expected by each endpoint in the [Swagger](https://app.logora.fr/docs). +::: + +### Anonymize a user + +```http +POST /api/v1/users/{user_uid}/anonymize +Authorization: Bearer YOUR_TOKEN +``` + +Anonymizes all personal data while preserving contributions. + +### Delete a user + +```http +DELETE /api/v1/users/{user_uid} +Authorization: Bearer YOUR_TOKEN +``` + +### Statistics + +```http +GET /api/v1/stats/{resource}?filter=day&from_date=2026-01-01&to_date=2026-01-31 +Authorization: Bearer YOUR_TOKEN +``` + +The `{resource}` segment must be one of: `users`, `groups`, `consultations`, `messages`, `proposals`, `votes`. + +| Parameter | Description | +|---|---| +| `filter` | Aggregation dimension. Default `day`. Examples: `day`, `week`, `month`. | +| `from_date` | Start date (objects created after this date). | +| `to_date` | End date. | + +Sample response: + +```json +[ + { "dimension": "2026-04-30", "value": 3 }, + { "dimension": "2026-04-29", "value": 7 } +] +``` + +:::tip Daily granularity +For daily stats (not an aggregated total), pass `filter=day`. +::: + +## Moderation via API + +The Logora admin handles moderation internally, but if you have your own external moderation queue you can also consume these endpoints: + +```http +GET /api/v1/moderation_entries +PATCH /api/v1/moderation_entries/{id} +GET /api/v1/moderation_entries/lock +``` + +The PATCH accepts a body with `status`, `moderation_reason`, `is_moderated`. + +## See also + +- **[Interactive Swagger](https://app.logora.fr/docs)** — full list of 51 endpoints with "Try it out" +- [Pre-render API (`render.logora.fr`)](/faq/render-api) — to fetch pre-rendered HTML for newsletters and homepages +- [User deletion and anonymization](/faq/account-deletion) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/faq/performance.md b/i18n/en/docusaurus-plugin-content-docs/current/faq/performance.md index c25d882..66ad726 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/faq/performance.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/faq/performance.md @@ -1,22 +1,66 @@ --- id: performance title: Performance +description: Logora optimizes its scripts to minimize their impact on your page load. --- -Logora makes every effort to ensure that the inserted code has the least impact on page loading. Here are some details on how Logora works to better evaluate the performance of inserted scripts. +Logora does everything to minimize the impact of embedded code on your page load. Here are the technical details to evaluate the performance of the embedded scripts. -#### Synthesis at the foot of the article +## 1) Article footer synthesis -The synthesis code inserted in your articles proceeds in four steps: -1. Download the script **embed.js**. This file, served from the DigitalOcean CDN, has a size of **8 Ko**. It allows you to launch Logora's functionalities and manages the calls to our API. -2. Call to the Logora API to check if a debate matches the page in question. This call returns the HTML code of the summary if there is an associated debate. The response has a size of **9 Ko**, and the median response time is **10ms**. Our servers are located in Paris. -3. Inserting the code in the page. The code is pre-rendered by the server, so it can be inserted directly without additional processing. -4. (Optional) For the first call of the page only, the script sends the metadata of the page (title, tags, description) to our servers to associate debates to the articles more easily. +The synthesis script is served from: -> If you have high performance constraints, use the [insert synthesis server side](installation/api.md) +``` +https://api.logora.fr/synthese.js +``` -#### Debate space +The synthesis code embedded in your articles works in four steps: -The debate space code proceeds in the same way as the synthesis code, but downloads more scripts and makes more calls to our API based on the user's actions and navigation. The initial **debat.js** file has a size of **60 KB**. +1. **Download the script** `synthese.js` +2. **Logora API call** to check if a debate matches the page. The response includes the synthesis HTML if a debate is associated. The HTML is pre-rendered by the server. +3. **Insert the code into the page** without further client-side processing. +4. **(Optional)** On the first call only, the script sends page metadata (title, tags, description) to ease article/debate matching. -Our API has a median response time of **15ms** (95th percentile 50ms) and handle several million requests per day. +:::tip High performance constraints? +Use [server-side install](/installation/server-side-sdk) to fetch the HTML directly into your template, with no client call. See also [Pre-render API](/faq/render-api). +::: + +## 2) Debate space + +The debate space script is served from: + +``` +https://api.logora.fr/debat.js +``` + +The code follows the same principle but downloads more scripts and makes more API calls based on user actions. + +## 3) Preconnect to Logora domains + +To speed up first load, preconnect to Logora domains in your ``: + +```html + + + + +``` + +These directives tell the browser to establish TCP/TLS connections in parallel with HTML parsing. + +## 4) Measure real impact + +To measure Logora's impact on your pages: + +1. **Pagespeed Insights**: [pagespeed.web.dev](https://pagespeed.web.dev/) — compare with and without Logora +2. **Chrome DevTools > Performance**: session recording to see CPU cost +3. **Real User Monitoring**: tag Logora pages in Datadog, New Relic, or Contentsquare + +--- + +## See also + +- [Server-side install](/installation/server-side-sdk) (no client call) +- [Comments module](/installation/module-commentaires) +- [Javascript install](/installation/javascript-sdk) +- [Pre-render API (`render.logora.fr`)](/faq/render-api) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/faq/render-api.md b/i18n/en/docusaurus-plugin-content-docs/current/faq/render-api.md new file mode 100644 index 0000000..01ad334 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/current/faq/render-api.md @@ -0,0 +1,204 @@ +--- +id: render-api +title: Pre-render API (render.logora.fr) +description: Fetch pre-rendered HTML for Logora debates, comments, and widgets to embed in newsletters, custom homepages, or third-party CMS. +sidebar_label: Pre-render API +--- + +`render.logora.fr` is Logora's **pre-render API**. It returns the HTML of a widget (debate, comments, vote, consultation…) directly, without loading the JavaScript on the client. + +:::tip Common use cases +- Embed an article's **comments** into a newsletter +- Build a **custom homepage** highlighting the most active debates +- Server-side pre-rendering for **SEO** (HTML is served directly to crawlers) +- Plug into a **third-party CMS** (Livingdocs, Drupal, etc.) that consumes HTML +::: + +Full interactive documentation: **[https://render.logora.fr/docs](https://render.logora.fr/docs)**. + +## Authentication + +None. Render endpoints accept anonymous requests — access control is enforced via your application's `shortname`. + +## Common parameters + +All endpoints share these query string parameters: + +| Parameter | Type | Required | Description | +|---|---|---|---| +| `shortname` | string | ✅ | Your application's shortname (visible in the Logora admin) | +| `language` | string | optional | Display language: `fr`, `en`, `es`, or `de`. Default `fr`. | +| `noHtml` | boolean | optional | If `true`, returns metadata only (no compiled HTML) | + +## Endpoints + +### `POST /synthesis` — debate synthesis + +Returns the HTML of the debate synthesis associated with a page on your site. + +| Parameter | Type | Required | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `uid` | string | ✅ | Page identifier (your source's `uid`) | +| `device` | string | optional | `mobile`, `tablet`, or `desktop` (responsive code) | +| `language` | string | optional | | +| `noHtml` | boolean | optional | | + +**Response**: + +```json +{ + "success": true, + "debate": { + "id": 123, + "slug": "my-debate", + "name": "My debate ?", + "direct_url": "https://example.com/debate/my-debate", + "type": "Group" + }, + "content": "
...
" +} +``` + +**Real production example**: + +``` +POST https://render.logora.fr/synthesis?shortname=milenio-4e7a57&uid=2050434 +``` + +### `POST /embed/comments` — comments module + +Returns the HTML of the comments module for a source. + +| Parameter | Type | Required | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `uid` | string | ✅ | Source unique identifier | +| `language` | string | optional | | +| `noHtml` | boolean | optional | | + +**Real production example**: + +``` +POST https://render.logora.fr/embed/comments?shortname=krone-f8d02e&uid=3930450&device=tablet +``` + +### `GET /app` — full debate space page + +Returns the full HTML of a debate space page (e.g. a debate landing page). + +| Parameter | Type | Required | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `url` | string | ✅ | URL of the page to render | + +### `POST /widget` — debate widget (by `uid`) + +| Parameter | Type | Required | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `uid` | string | optional | | +| `language` | string | optional | | +| `noHtml` | boolean | optional | | + +### `POST /embed/argument` — single argument widget + +| Parameter | Type | Required | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `id` | string | ✅ | Argument identifier | +| `language` | string | optional | | +| `noHtml` | boolean | optional | | + +### `POST /embed/group` — debate widget (by `id`) + +| Parameter | Type | Required | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `id` | string | ✅ | Group (debate) identifier | +| `language` | string | optional | | +| `noHtml` | boolean | optional | | + +### `POST /embed/proposal` — proposal widget + +| Parameter | Type | Required | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `id` | string | ✅ | Proposal identifier | +| `language` | string | optional | | +| `noHtml` | boolean | optional | | + +### `POST /embed/consultation` — consultation widget + +| Parameter | Type | Required | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `id` | string | ✅ | Consultation slug | +| `language` | string | optional | | +| `noHtml` | boolean | optional | | + +### `POST /embed/vote` — vote widget + +| Parameter | Type | Required | Description | +|---|---|---|---| +| `shortname` | string | ✅ | | +| `id` | string | ✅ | Vote identifier | +| `language` | string | optional | | +| `noHtml` | boolean | optional | | + +## Response shape + +Most `/embed/*` endpoints return: + +```json +{ + "success": true, + "resource": { "id": 1234 }, + "html": "
...
" +} +``` + +`/synthesis` and `/widget` return a slightly different payload (with a `debate` key instead of `resource`, and `content` instead of `html`). + +## Errors + +On failure, the response is: + +```json +{ + "success": false, + "error": "Data fetching error" +} +``` + +## Full example: top debates in a newsletter + +```javascript +// On your newsletter server (Node.js for instance) + +// 1. Fetch the latest debates via the public API +const groupsRes = await fetch( + 'https://app.logora.fr/api/v1/groups?per_page=3', + { headers: { 'Authorization': `Bearer ${accessToken}` } } +); +const { data: debates } = await groupsRes.json(); + +// 2. For each debate, fetch the pre-rendered synthesis +const blocks = await Promise.all(debates.map(async (d) => { + const res = await fetch( + `https://render.logora.fr/synthesis?shortname=YOUR_APP&uid=${d.uid}`, + { method: 'POST' } + ); + const { content } = await res.json(); + return content; +})); + +// 3. Inject into your email template +const newsletterHtml = template.replace('{{debates}}', blocks.join('')); +``` + +## See also + +- **[Render API Swagger](https://render.logora.fr/docs)** +- [Public Logora API](/faq/api) — for classic REST endpoints +- [Server-side install](/installation/server-side-sdk) — alternative for embedding HTML in your article pages diff --git a/i18n/en/docusaurus-plugin-content-docs/current/installation/webview.md b/i18n/en/docusaurus-plugin-content-docs/current/installation/webview.md index bcfe211..16efe2c 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/installation/webview.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/installation/webview.md @@ -1,27 +1,26 @@ --- id: webview title: Webview +description: Embed Logora in a WebView inside a mobile app (iOS / Android / React Native) with SSO. --- -# Logora Integration in WebView with SSO +Embedding Logora in a WebView lets you display the debate space directly inside a mobile app or a website in WebView mode, while preserving Logora's interactive features. -## Introduction - -Integrating Logora in a WebView allows you to display the debate space directly within a mobile application or a website in WebView mode. This approach ensures a seamless user experience while maintaining Logora's interactive features. - -Two applications have already successfully integrated Logora: **Der SPIEGEL** and **Suedkurier**. You can see these integrations in action at the following links: +Two apps have already integrated Logora successfully: **Der SPIEGEL** and **Suedkurier**. See them in action: - [Der SPIEGEL](https://www.loom.com/share/725de75c09d64911ad42fdff7acf07e7?sid=c5d01191-5783-4980-be81-f1a21e162e87) - [Suedkurier](https://www.loom.com/share/b3eabe7ab0d1417f8cbbfd29735c2adf?sid=356bc7c1-559e-4f2e-bece-7cecc328cb6e) -For seamless user authentication, Logora supports Single Sign-On (SSO) through the injection of a token into the `logora_config` object under the `remote_auth` parameter. **Logora's SSO integration is only compatible with the JWT authentication method.** +:::warning JWT-only SSO +Logora's SSO integration in WebView is **only compatible with JWT**. See [JWT Authentication](/authentication/jwt). +::: -## 1. Installing Logora in WebView +## 1. Installing Logora in a WebView -Installing Logora in a WebView follows the same procedure as the standard installation by inserting the standard JavaScript code. Here are the steps to follow: +Installation follows the same procedure as the standard JavaScript install. -### 1.1 Creating the WebView +### 1.1 Create the WebView page -In your mobile application or WebView site, create a web view that loads the URL of the debate space. Example in HTML: +Host on your site a minimal page that loads the Logora widget: ```html @@ -32,11 +31,11 @@ In your mobile application or WebView site, create a web view that loads the URL Logora Debate - +
@@ -44,9 +43,9 @@ In your mobile application or WebView site, create a web view that loads the URL ``` -### 1.2 Loading in a Mobile WebView +### 1.2 Load in a mobile WebView -**Example in Swift (iOS)** +#### iOS (Swift) ```swift import UIKit @@ -54,13 +53,13 @@ import WebKit class DebateViewController: UIViewController { var webView: WKWebView! - + override func loadView() { webView = WKWebView() webView.configuration.preferences.javaScriptEnabled = true view = webView } - + override func viewDidLoad() { super.viewDidLoad() let url = URL(string: "https://yoursite.com/debate-space")! @@ -69,11 +68,10 @@ class DebateViewController: UIViewController { } ``` -**Example in Kotlin (Android)** +#### Android (Kotlin) ```kotlin import android.os.Bundle -import android.webkit.WebSettings import android.webkit.WebView import androidx.appcompat.app.AppCompatActivity @@ -88,48 +86,94 @@ class DebateActivity : AppCompatActivity() { } ``` -## 2. Single Sign-On (SSO) in WebView +#### React Native -For users to be automatically authenticated on Logora without re-entering their credentials, it is necessary to inject a token into `remote_auth` within `logora_config`. +```javascript +import { WebView } from 'react-native-webview'; -**Logora's SSO integration is only compatible with JWT authentication.** + +``` -You can check the detailed guide on authentication in our [JWT documentation](../../authentication/jwt). +## 2. SSO authentication in WebView -Example configuration: +For users to be authenticated automatically on Logora without re-entering credentials, inject a token into `remote_auth` within `logora_config`. ```javascript var logora_config = { - shortname: "APPLICATION_NAME", - remote_auth: "YOUR_JWT_SSO_TOKEN" + shortname: "APP_NAME", + remote_auth: "YOUR_JWT_TOKEN" }; ``` -### User Logout +See the full guide: [JWT Authentication](/authentication/jwt). + +### Logout -To log out a user, simply remove the `remote_auth` value or pass an empty string: +To log the user out, remove `remote_auth` or pass an empty string: ```javascript var logora_config = { - shortname: "APPLICATION_NAME", + shortname: "APP_NAME", remote_auth: "" }; ``` -## 3. Initial Route +## 3. Adapt the rendering to WebView via a URL parameter + +To distinguish your WebView from your responsive web view, a common convention (used for example by Cronista) is to add a parameter to the URL of the page hosting the WebView: + +``` +https://yoursite.com/debate-space?outputType=webapp-type +``` + +Logora does not process this parameter directly — it's up to your integration to detect it and adapt behavior (custom CSS, link interception, etc.). + +### Example: intercept internal links and forward them to the native router -To define the first page that will be displayed in the webview, you can pass the desired path as the `initial_path` parameter: +Logora dispatches a `logoraContentLoaded` DOM event once the widget is rendered. You can use it to adjust link behavior: ```javascript -var logora_config = { - shortname: "APPLICATION_NAME", - initial_path: "/debate/debate-title" -}; +window.addEventListener("logoraContentLoaded", () => { + const currentUrl = new URL(window.location.href); + if (currentUrl.searchParams.get("outputType") !== "webapp-type") return; + + const root = document.querySelector("#logoraRoot"); + if (!root) return; + + const links = root.querySelectorAll("a[href]"); + links.forEach((link) => { + link.addEventListener("click", (e) => { + e.preventDefault(); + // Forward URL to native router + window.ReactNativeWebView?.postMessage(link.href); + // or for native iOS / Android: + // window.webkit?.messageHandlers?.linkHandler?.postMessage(link.href); + }); + }); +}); ``` -The application will then open directly on this page. This provides a direct link to the debate space pages. +On the native side, listen for messages and run your custom navigation. +## 4. Session persistence + +If your app uses a native session manager (Piano, Auth0, internal OAuth): + +1. Get the native session token when opening the WebView +2. Regenerate a Logora JWT from the native claims (server-side) +3. Pass it as `remote_auth` as above + +:::caution Don't try to share native cookies +Cookie sharing between the native app and WebView is fragile and blocked by iOS/Android in most cases. **Always** use a freshly regenerated JWT each time the WebView opens. +::: + +--- -## Conclusion +## See also -With this integration, your users can interact on Logora without friction, directly from your application or WebView site while benefiting from secure and seamless authentication through JWT-based SSO. +- [JWT Authentication](/authentication/jwt) +- [Appearance and theme](/configuration/theme) +- [Pre-render API](/faq/render-api) — alternative to fetch pre-rendered HTML server-side diff --git a/sidebars.json b/sidebars.json index 93b1292..9ea77fd 100644 --- a/sidebars.json +++ b/sidebars.json @@ -31,7 +31,24 @@ "type": "category", "label": "FAQ", "collapsed": false, - "items": ["faq/quality", "faq/performance", "faq/data", "faq/mailing", "faq/tracking", "faq/registration", "faq/thesis", "faq/invitation", "faq/journalist", "faq/share", "faq/premium", "faq/iframes", "faq/errors"] + "items": [ + "faq/quality", + "faq/performance", + "faq/data", + "faq/account-deletion", + "faq/mailing", + "faq/tracking", + "faq/registration", + "faq/thesis", + "faq/invitation", + "faq/journalist", + "faq/share", + "faq/premium", + "faq/iframes", + "faq/api", + "faq/render-api", + "faq/errors" + ] } ] } diff --git a/src/css/custom.css b/src/css/custom.css index daebe91..5c79936 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -1,27 +1,383 @@ -:root{ - --ifm-color-primary: #417ec7; - --ifm-color-primary-dark: #3671b8; - --ifm-color-primary-darker: #336bad; - --ifm-color-primary-darkest: #2a588f; - --ifm-color-primary-light: #558ccd; - --ifm-color-primary-lighter: #6093d0; - --ifm-color-primary-lightest: #7ea8d9; +/** + * Logora Documentation — Custom CSS + * Improved design for better readability + * 2026-04 update + */ + +/* ============================================================ + Theme colors + ============================================================ */ + +:root { + /* Primary — Logora blue */ + --ifm-color-primary: #417ec7; + --ifm-color-primary-dark: #3671b8; + --ifm-color-primary-darker: #336bad; + --ifm-color-primary-darkest: #2a588f; + --ifm-color-primary-light: #558ccd; + --ifm-color-primary-lighter: #6093d0; + --ifm-color-primary-lightest: #7ea8d9; + + /* Typography */ + --ifm-font-family-base: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", + Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + --ifm-font-family-monospace: ui-monospace, "SF Mono", "Cascadia Code", + "Roboto Mono", Menlo, Monaco, Consolas, monospace; + --ifm-font-size-base: 16px; + --ifm-line-height-base: 1.65; + --ifm-heading-line-height: 1.3; + --ifm-heading-font-weight: 700; + + /* Spacing */ + --ifm-spacing-horizontal: 1.25rem; + --ifm-paragraph-margin-bottom: 1.25rem; + + /* Code */ + --ifm-code-font-size: 90%; + --ifm-code-padding-horizontal: 0.4em; + --ifm-code-padding-vertical: 0.15em; + --ifm-code-background: rgba(65, 126, 199, 0.08); + --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); + + /* Borders & shadows */ + --ifm-global-radius: 8px; + --ifm-card-border-radius: 12px; + --ifm-toc-border-color: #e6e8eb; + --ifm-table-border-color: #e6e8eb; +} + +[data-theme='dark'] { + --ifm-background-color: #0f1419; + --ifm-background-surface-color: #161b22; + --ifm-code-background: rgba(110, 158, 220, 0.12); + --ifm-toc-border-color: #2a313a; + --ifm-table-border-color: #2a313a; + --docusaurus-highlighted-code-line-bg: rgba(255, 255, 255, 0.08); } +/* ============================================================ + Typography + ============================================================ */ + +article h1 { + font-size: 2.25rem; + margin-bottom: 1.5rem; + letter-spacing: -0.02em; +} + +article h2 { + font-size: 1.625rem; + margin-top: 3rem; + margin-bottom: 1rem; + letter-spacing: -0.01em; + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--ifm-toc-border-color); +} + +article h3 { + font-size: 1.25rem; + margin-top: 2rem; + margin-bottom: 0.75rem; +} + +article h4 { + font-size: 1.05rem; + margin-top: 1.5rem; +} + +article p { + font-size: 1rem; + color: var(--ifm-color-content); +} + +article a { + text-decoration: none; + border-bottom: 1px dashed var(--ifm-color-primary); + transition: all 0.15s ease; +} + +article a:hover { + border-bottom-style: solid; + background: rgba(65, 126, 199, 0.08); +} + +/* ============================================================ + Code blocks + ============================================================ */ + +.theme-code-block { + border-radius: var(--ifm-global-radius); + margin: 1.5rem 0; + font-size: 0.875rem; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); +} + +.prism-code { + padding: 1rem 1.25rem !important; +} + +code { + border-radius: 4px; + border: 1px solid rgba(65, 126, 199, 0.15); +} + +/* ============================================================ + Tables + ============================================================ */ + +article table { + display: table; + width: 100%; + margin: 1.5rem 0; + border-collapse: collapse; + font-size: 0.95rem; + border-radius: var(--ifm-global-radius); + overflow: hidden; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); +} + +article table thead { + background: linear-gradient(180deg, #f7f9fc 0%, #eef2f7 100%); +} + +[data-theme='dark'] article table thead { + background: linear-gradient(180deg, #1d2530 0%, #181f29 100%); +} + +article table th { + font-weight: 600; + text-transform: none; + padding: 0.85rem 1rem; + border: 1px solid var(--ifm-table-border-color); +} + +article table td { + padding: 0.75rem 1rem; + border: 1px solid var(--ifm-table-border-color); + vertical-align: top; +} + +article table tr:nth-child(even) td { + background: rgba(65, 126, 199, 0.025); +} + +[data-theme='dark'] article table tr:nth-child(even) td { + background: rgba(110, 158, 220, 0.04); +} + +/* ============================================================ + Admonitions (tips, warnings, notes) + ============================================================ */ + +.theme-admonition { + border-radius: var(--ifm-global-radius); + border-left-width: 4px; + margin: 1.5rem 0; + padding: 1rem 1.25rem; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); +} + +.theme-admonition-tip { + background: rgba(75, 181, 67, 0.08); +} + +.theme-admonition-warning { + background: rgba(255, 165, 0, 0.08); +} + +.theme-admonition-info { + background: rgba(65, 126, 199, 0.08); +} + +.theme-admonition-danger { + background: rgba(220, 53, 69, 0.08); +} + +.theme-admonition-note { + background: rgba(108, 117, 125, 0.06); +} + +.theme-admonition-caution { + background: rgba(255, 193, 7, 0.08); +} + +/* ============================================================ + Blockquotes + ============================================================ */ + +article blockquote { + border-left: 4px solid var(--ifm-color-primary); + padding: 0.75rem 1.25rem; + background: rgba(65, 126, 199, 0.04); + border-radius: 0 var(--ifm-global-radius) var(--ifm-global-radius) 0; + margin: 1.5rem 0; + font-style: normal; +} + +article blockquote p { + margin-bottom: 0; +} + +/* ============================================================ + Sidebar + ============================================================ */ + +.menu { + font-size: 0.92rem; + padding: 1rem 0.75rem; +} + +.menu__link { + border-radius: 6px; + padding: 0.45rem 0.75rem; + transition: all 0.12s ease; +} + +.menu__link:hover { + background: rgba(65, 126, 199, 0.08); +} + +.menu__link--active { + background: rgba(65, 126, 199, 0.12); + font-weight: 600; +} + +.menu__list-item-collapsible:hover { + background: rgba(65, 126, 199, 0.04); + border-radius: 6px; +} + +/* ============================================================ + Breadcrumbs + ============================================================ */ + +.theme-doc-breadcrumbs { + margin-bottom: 1.5rem; + font-size: 0.875rem; + opacity: 0.7; +} + +/* ============================================================ + TOC (right sidebar) + ============================================================ */ + +.table-of-contents { + font-size: 0.875rem; +} + +.table-of-contents__link { + padding: 0.25rem 0; + transition: color 0.12s ease; +} + +.table-of-contents__link--active { + color: var(--ifm-color-primary); + font-weight: 600; +} + +/* ============================================================ + Pagination (next/previous) + ============================================================ */ + +.pagination-nav { + margin-top: 3rem; +} + +.pagination-nav__link { + border: 1px solid var(--ifm-toc-border-color); + border-radius: var(--ifm-global-radius); + padding: 1rem 1.25rem; + transition: all 0.15s ease; +} + +.pagination-nav__link:hover { + border-color: var(--ifm-color-primary); + background: rgba(65, 126, 199, 0.04); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(65, 126, 199, 0.1); +} + +.pagination-nav__sublabel { + font-size: 0.8rem; + opacity: 0.7; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.pagination-nav__label { + font-weight: 600; + margin-top: 0.25rem; +} + +/* ============================================================ + Navbar + ============================================================ */ + +.navbar { + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); + backdrop-filter: blur(10px); + background: rgba(255, 255, 255, 0.95); +} + +[data-theme='dark'] .navbar { + background: rgba(15, 20, 25, 0.95); +} + +.navbar__title { + font-weight: 700; + letter-spacing: -0.01em; +} + +/* ============================================================ + Footer + ============================================================ */ + +.footer { + background: linear-gradient(180deg, #f7f9fc 0%, #eef2f7 100%); + padding: 2rem 0; +} + +[data-theme='dark'] .footer { + background: linear-gradient(180deg, #161b22 0%, #0f1419 100%); +} + +.footer__copyright { + font-size: 0.875rem; + opacity: 0.7; +} + +/* ============================================================ + GitHub link in navbar (preserved from original) + ============================================================ */ + .header-github-link:hover { - opacity: 0.6; + opacity: 0.6; +} + +.header-github-link::before { + content: ''; + width: 24px; + height: 24px; + display: flex; + background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") + no-repeat; +} + +[data-theme='dark'] .header-github-link::before { + background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") + no-repeat; +} + +/* ============================================================ + Responsive tweaks + ============================================================ */ + +@media (max-width: 996px) { + article h1 { + font-size: 1.875rem; } - - .header-github-link::before { - content: ''; - width: 24px; - height: 24px; - display: flex; - background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") - no-repeat; + + article h2 { + font-size: 1.375rem; } - - [data-theme='dark'] .header-github-link::before { - background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") - no-repeat; - } \ No newline at end of file +}