Activar la creación/inicio de sesión en un servicio externo cuando un usuario inicia sesión en discourse

Tengo un plugin de JS en mi sitio de Discourse. Este JS utiliza PocketBase para almacenar datos. PocketBase es un servicio “serverless” de SQLite que me permite almacenar archivos y blobs JSON. PocketBase tiene un sistema de autenticación fantástico basado en JWT, de modo que una vez que un usuario tiene un token de autenticación, puede almacenar datos de forma segura directamente en PocketBase (sin pasar por un servidor backend, etc.) directamente desde JS en el lado del cliente.

Estoy tratando de encontrar una manera de generar un inicio de sesión en el lado de PocketBase automáticamente cuando un usuario inicia sesión en Discourse.

Mi primer intento fue hacer que el plugin de JS realizara una llamada a una ruta en el servidor e incluyera las cookies de autenticación de Discourse. Luego, para esa ruta, dejar que nginx la proxy a un servicio (“login proxy”) que pueda decodificar las cookies de autenticación y determinar quién es el usuario. Con la información del usuario verificada, el “login proxy” puede hacer una llamada especial a PocketBase y obtener una cookie de autenticación de PocketBase, y luego enviar esa cookie de autenticación de PocketBase que el JS del lado del cliente puede usar para realizar solicitudes posteriores directamente a PocketBase.

Pero, estoy teniendo problemas para decodificar las cookies de autenticación de Discourse (imagino que _t es la cookie correcta, pero no veo una forma sencilla de acceder a los detalles del usuario y me preocupa que la estructura pueda cambiar de todos modos).

¿Hay una forma más inteligente de acceder de forma segura a la dirección de correo electrónico de un usuario que ha iniciado sesión? No creo que esto deba ocurrir en el lado del cliente, y preferiría hacerlo en el lado del servidor por razones de seguridad obvias.

No conozco lo suficiente de tu stack o caso de uso, pero creo que he resuelto un problema similar antes y algunas ideas pueden serte útiles.

Tengo una aplicación Next.js donde necesito que el lado del cliente tenga un JWT válido para hacer llamadas a mi API de backend si hay una sesión de Discourse.

Para esto, utilizo Discourse como mi proveedor de identidad a través de DiscourseConnect.

En mi caso, lo hago con una única llamada fetch del lado del cliente con { credentials: "include" }, lo que solo funciona porque tengo todo configurado con un solo dominio y la llamada fetch sigue las redirecciones de forma transparente.

Mi cliente llama a un /auth/token personalizado, que verifica la existencia de _t (solo para evitar una redirección inútil en caso contrario) y devuelve una redirección a una URL segura /session/sso_provider construida siguiendo la documentación del tema enlazado, con nonce/sso/sig, y un return_sso_url que apunta a un /auth/callback personalizado, que extraerá los datos enviados por Discourse, construirá y devolverá un token JWT que mi cliente podrá usar a partir de ese momento.

Creo que tu caso de uso puede resolverse de manera similar.

Genial, lo intentaré. Muchas gracias.

1 me gusta

@renato Algunas preguntas si no te importa.

¿Significa esto que necesito delegar toda la autenticación de gestión de usuarios a la aplicación connect? No estoy seguro de querer hacer esto, si es así.

Si connect es solo una capa adicional sobre la gestión de autenticación de usuarios de discourse existente, entonces eso parece factible.

Pero, cuando empecé a leer sobre discourse connect, me preocupa tener que construir y mantener una aplicación completamente nueva para gestionar la autenticación de usuarios, y realmente no sé cómo enfocar esto ahora mismo.

Mi respuesta asume que estás utilizando Discourse como tu proveedor de identidad (con sus interfaces de usuario de inicio de sesión/registro), y quieres que siga siendo así.

En el lado de Discourse, habilitarlo es tan simple como

Sin embargo, mencionaste que estás creando un plugin.

Si creas “una ruta en el servidor” en una nueva acción de controlador en un plugin de Discourse, puedes obtener el usuario de la sesión, llamar a terceros y devolver el JWT a tu cliente.

Muchas gracias por la discusión.

Leí el enlace: Use Discourse as an identity provider (SSO, DiscourseConnect) - #8 by reverend_paco

Pero, creo que esto es si quiero enviar a mis usuarios a un sitio secundario y gestionar la autenticación allí.

En mi caso, tengo un fragmento de JS que se ejecuta en el sitio de Discourse. Y, quiero que ese JS llame a una ruta en el mismo servidor y reciba una cookie para Pocketbase.

De hecho, utilizo un proxy Nginx delante de Discourse, y acabo de añadir una ruta especial /pb/auth (por ejemplo). Cuando mi JS accede a esa ruta, un servidor proxy backend (que no está dentro de Discourse) acepta esa conexión e intenta decodificar la cookie de sesión _t.

Lo estaba haciendo de esta manera porque parece un poco más fácil que añadir un plugin de Discourse (tengo menos familiaridad con eso y la configuración de desarrollo, etc.). Si es una cuestión simple de decodificar una cookie usando base64 y hash SHA, pensé que eso me daría una carga útil segura para decirme quién es el usuario.

Pero, si crees que hay una forma sencilla de crear un plugin que añada esta ruta a Discourse, estoy muy interesado en probarlo. Parece la forma correcta a largo plazo. Pero, soy un viejo programador de Perl, así que prefiero la ruta fácil, y mi ruta de Nginx parecía más fácil. :slight_smile:

Todo lo contrario: si tienes un “sitio” separado (PocketBase en este ejemplo) y quieres que Discourse sea la fuente de verdad para la gestión de usuarios/autenticación, como mi ejemplo de Next.js.

Empezaría por leer

Genial, me entusiasma leer eso. Empecé a mirar el plugin esqueleto de ejemplo (GitHub - discourse/discourse-plugin-skeleton: Template for Discourse plugins) y me decepcionó un poco porque no tiene ninguna documentación.

A primera vista, necesito preguntar: ¿este tutorial añade código a la instalación base de Rails para Discourse? No me importa hacerlo si esa es la forma oficial, pero parece peligroso y sería mejor manejarlo como un plugin (que se puede desinstalar o deshabilitar fácilmente). Además, ¿no tengo que preocuparme de que esto rompa las actualizaciones de Discourse si mi código no está en el repositorio de GitHub?

Por ejemplo, aquí:

¿Significa esto que realmente entro en el contenedor (./launcher enter app) y luego edito /var/www/app/controllers/snack_controller.rb?

Y, de hecho, acabo de seguir esas instrucciones. No puedo hacer que la ruta /admin/snack.json funcione, incluso después de ejecutar ./launcher rebuild app.

Este tutorial parece tener unos ocho años. ¿Es esta realmente la forma correcta de hacer las cosas?

Hay otras guías, la fecha en la parte superior es la de creación del tema, pero todo lo que esté en Documentation debería estar actualizado. Avísanos si encuentras algún problema.

Puedes consultar el código del Plugin existente como referencia.

No:

Tengo la impresión de que esto aún no se ha actualizado

Creo que el archivo ahora es https://github.com/discourse/discourse/blob/main/app/assets/javascripts/admin/addon/routes/admin-route-map.js
y se le cambió el nombre en 2020.

2 Me gusta

Ok, intenté seguir las instrucciones. Intenté usar este comando rake plugin:create[pocketbase-auth] dentro del contenedor (ya que rake no estaría disponible fuera, ¿verdad?). Pero falla estrepitosamente porque no tengo git configurado dentro del contenedor.

Según leí más adelante, parece que necesito especificar el repositorio git del plugin para mostrar un plugin en la sección de administración. Pero, aún no he llegado al punto de tener una versión mínimamente funcional del plugin y no tengo las cosas dentro de un repositorio git.

EDITAR: No leí atentamente, y de hecho esto requiere una configuración de desarrollo, lo cual está claramente indicado desde el principio. Trabajaré en eso y volveré a esto.

Sospecho que estas dificultades se deben a que típicamente los plugins se desarrollan contra una configuración “dev” de discourse, no dentro del contenedor docker que estoy ejecutando. Eso está bien, pero desearía que las guías de desarrollo de plugins comenzaran con esa suposición e instrucciones sobre cómo ejecutar de esa manera. La forma recomendada de ejecutar discourse es usando docker (lo cual me encanta), pero creo que hay una brecha entre cómo ejecutar cosas dentro de docker y hacer desarrollo dentro de la documentación.

# rake plugin:create[pocketbase-auth]
Clonando 'https://github.com/discourse/discourse-plugin-skeleton' a '/var/www/discourse/plugins/pocketbase-auth'...
Inicializando el repositorio git...
hint: Usando 'master' como nombre para la rama inicial. Este nombre de rama predeterminado
hint: está sujeto a cambios. Para configurar el nombre de la rama inicial a usar en todas
hint: tus nuevos repositorios, lo que suprimirá esta advertencia, llama a:
hint:
hint:   git config --global init.defaultBranch <nombre>
hint:
hint: Los nombres comúnmente elegidos en lugar de 'master' son 'main', 'trunk' y
hint: 'development'. La rama recién creada se puede renombrar con este comando:
hint:
hint:   git branch -m <nombre>
Repositorio Git vacío inicializado en /var/www/discourse/plugins/pocketbase-auth/.git/
Identidad del autor desconocida

*** Por favor, dime quién eres.

Ejecuta

  git config --global user.email "tú@ejemplo.com"
  git config --global user.name "Tu Nombre"

para establecer la identidad predeterminada de tu cuenta.
Omite --global para establecer la identidad solo en este repositorio.
error: no se puede detectar automáticamente la dirección de correo electrónico (obtenido 'discourse@community-public-do-vm-app.(none)')
rake abortó!
Comando fallido con salida 128: git
/var/www/discourse/lib/tasks/plugin.rake:356:in `system'
/var/www/discourse/lib/tasks/plugin.rake:356:in `block (2 levels) in <main>'
/var/www/discourse/lib/tasks/plugin.rake:346:in `chdir'
/var/www/discourse/lib/tasks/plugin.rake:346:in `block in <main>'
/usr/local/bin/bundle:25:in `load'
/usr/local/bin/bundle:25:in `<main>'
Tareas: TOP => plugin:create
(Ver traza completa ejecutando la tarea con --trace)

Una actualización: seguí tu consejo y desarrollé un plugin. Funciona muy bien y hace exactamente lo que necesitaba. Gracias por tu ayuda.

1 me gusta

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.