Cómo conectar cuentas de usuario y enrutamiento autenticado en Joystick

Cómo crear cuentas de usuario en Joystick, iniciar sesión de usuarios y ayudarlos a restablecer su contraseña, así como también cómo crear rutas protegidas que redirigen según el estado de inicio de sesión de un usuario.

Primeros pasos

Para este tutorial, vamos a utilizar el marco JavaScript de pila completa de CheatCode, Joystick. Joystick reúne un marco de interfaz de usuario de front-end con un back-end de Node.js para crear aplicaciones.

Para comenzar, querremos instalar Joystick a través de NPM. Asegúrese de estar usando Node.js 16+ antes de instalar para garantizar la compatibilidad (lea este tutorial primero si necesita aprender a instalar Node.js o ejecutar varias versiones en su computadora):

Terminal

npm i -g @joystick.js/cli

Esto instalará Joystick globalmente en su computadora. Una vez instalado, vamos a crear un nuevo proyecto:

Terminal

joystick create app

Después de unos segundos, verá un mensaje desconectado de cd en su nuevo proyecto y ejecute joystick start :

Terminal

cd app && joystick start

Después de esto, su aplicación debería estar ejecutándose y estamos listos para comenzar.

Agregando algo de CSS global

Antes de profundizar en la lógica de nuestras cuentas de usuario, muy rápido, vamos a agregar algo de CSS global para limpiar nuestra interfaz de usuario:

/index.css

*, *:before, *:after {
  box-sizing: border-box;
}

body {
  font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif;
  font-size: 16px;
  background: #fff;
  margin: 20px;
}

form {
  width: 100%;
  max-width: 400px;
}

.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 20px;
}

.form-field {
  margin-bottom: 20px;
}

label {
  font-size: 15px;
  font-weight: bold;
  display: block;
  margin-bottom: 10px;
  display: flex;
}

label a {
  display: inline-block;
  margin-left: auto;
  font-weight: normal;
  color: #aaa;
}

input {
  width: 100%;
  max-width: 100%;
  border: 1px solid #ddd;
  padding: 10px 15px;
  border-radius: 3px;
  font-size: 16px;
}

input:focus {
  outline: 0;
  border: 1px solid #0099ff;
  box-shadow: 0px 0px 0px 3px rgba(0, 153, 255, 0.3);
}

.input-hint {
  font-size: 14px;
  margin-bottom: 0px;
}

.input-hint.error {
  color: red;
}

button {
  padding: 10px 15px;
  font-size: 16px;
  background: #0099ff;
  color: #fff;
  border-radius: 3px;
  border: none;
}

Más adelante en el tutorial, nuestra interfaz de usuario consistirá únicamente en formularios utilizados para administrar la cuenta de un usuario. Para que nuestra interfaz de usuario sea más fácil de entender, arriba, estamos agregando algo de CSS global en el /index.css archivo en la raíz de nuestra aplicación. Joystick carga automáticamente este archivo en el /index.html archivo en la raíz de nuestro proyecto (la plantilla HTML base representada para todas las páginas de su aplicación).

Además de los estilos de formulario, también hemos agregado algunos reinicios simples para el box-sizing atributo (esto asegura que se respeten el relleno y los márgenes en el navegador) y en el body elemento, establezca una fuente predeterminada, un tamaño de fuente e incluso haya agregado un pequeño margen al <body></body> por lo que nuestro contenido se desplaza un poco del borde del navegador.

Adición de rutas y páginas

Profundizando en el código, nuestro objetivo es conectar un conjunto de páginas para administrar todo el ciclo de vida de una cuenta. Ahora, queremos configurar una serie de rutas en el servidor que mostrarán las páginas que se muestran a los usuarios en el navegador:

  • /signup generará un formulario donde los usuarios pueden crear una nueva cuenta.
  • /login generará un formulario donde los usuarios pueden iniciar sesión en una cuenta existente.
  • /recover-password generará un formulario donde los usuarios pueden activar una solicitud de restablecimiento de contraseña para una cuenta existente.
  • /reset-password/:token generará un formulario donde el usuario puede ingresar una nueva contraseña y actualizar su registro de usuario en la base de datos.

Todas las rutas en una aplicación Joystick se pasan al node.app() objeto de opciones de la función, ubicado en el /index.server.js archivo en la raíz del proyecto:

/index.servidor.js

import node from "@joystick.js/node";
import api from "./api";

node.app({
  api,
  routes: {
    "/dashboard": (req, res) => {
      res.render("ui/pages/dashboard/index.js", {
        layout: "ui/layouts/app/index.js",
      });
    },
    "/signup": (req, res) => {
      res.render("ui/pages/signup/index.js", {
        layout: "ui/layouts/app/index.js",
      });
    },
    "/login": (req, res) => {
      res.render("ui/pages/login/index.js", {
        layout: "ui/layouts/app/index.js",
      });
    },
    "/recover-password": (req, res) => {
      res.render("ui/pages/recoverPassword/index.js", {
        layout: "ui/layouts/app/index.js",
      });
    },
    "/reset-password/:token": (req, res) => {
      res.render("ui/pages/resetPassword/index.js", {
        layout: "ui/layouts/app/index.js",
      });
    },
    "*": (req, res) => {
      res.render("ui/pages/error/index.js", {
        layout: "ui/layouts/app/index.js",
        props: {
          statusCode: 404,
        },
      });
    },
  },
});

De forma predeterminada, cuando ejecuta joystick create , la plantilla de proyecto que está configurada para usted tiene dos rutas definidas para nosotros / y * . El primero muestra un componente Joystick de ejemplo y el segundo muestra la página 404 o "error" que se muestra cuando no se puede encontrar una ruta coincidente para la URL actual.

Para nuestro trabajo, vamos a comenzar reemplazando el / route con una ruta que actuará como una página falsa de "inicio de sesión". En el código anterior, estamos haciendo algunas cosas:

  1. Intercambiando el / ruta con un /dashboard enrutar y renderizar una página definida como un componente Joystick en /ui/pages/dashboard/index.js .
  2. Para cada una de las páginas que describimos anteriormente, definir una ruta bajo el routes objeto pasado a las opciones para node.app() . Esta es la función utilizada por Joystick para iniciar un servidor Express.js para nosotros. Cuando ese servidor se inicia, cada una de las rutas que enumeramos en routes se agrega como una ruta HTTP GET.
  3. Para cada ruta, renderizar una página definida como un componente Joystick usando @joystick.js/ui en el /ui/pages directorio en la raíz de nuestra aplicación.

Para que esto funcione, debemos asegurarnos de que todas nuestras páginas estén definidas en el /ui/pages directorio.

A continuación, sigamos adelante y creemos algunas páginas de esqueleto como marcadores de posición (pasaremos la mayor parte del tutorial conectando estas después):

/ui/pages/dashboard/index.js

import ui from '@joystick.js/ui';

const Dashboard = ui.component({
  render: () => {
    return `
      <div>
        <p>Dashboard</p>
      </div>
    `;
  },
});

export default Dashboard;

/ui/pages/signup/index.js

import ui from '@joystick.js/ui';

const Signup = ui.component({
  render: () => {
    return `
      <div>
        <p>Signup</p>
      </div>
    `;
  },
});

export default Signup;

/ui/pages/login/index.js

import ui from '@joystick.js/ui';

const Login = ui.component({
  render: () => {
    return `
      <div>
        <p>Login</p>
      </div>
    `;
  },
});

export default Login;

/ui/pages/recoverPassword/index.js

import ui from '@joystick.js/ui';

const RecoverPassword = ui.component({
  render: () => {
    return `
      <div>
        <p>RecoverPassword</p>
      </div>
    `;
  },
});

export default RecoverPassword;

/ui/pages/resetPassword/index.js

import ui from '@joystick.js/ui';

const ResetPassword = ui.component({
  render: () => {
    return `
      <div>
        <p>ResetPassword</p>
      </div>
    `;
  },
});

export default ResetPassword;

Con eso en su lugar, ahora, si cargamos nuestra aplicación en el navegador en http://localhost:2600 y revise cada una de las rutas que hemos definido arriba, deberíamos ver nuestros componentes de marcador de posición.

Ahora, para comenzar a hacer que las cosas funcionen, vamos a conectar la página de registro.

Conexión de la página de registro

Como era de esperar, el /signup será donde nuestros usuarios puedan crear una cuenta. Para comenzar, agreguemos el marcado HTML para nuestra página y discutamos lo que está sucediendo y luego agreguemos la funcionalidad para crear una cuenta.

/ui/pages/signup/index.js

import ui from '@joystick.js/ui';

const Signup = ui.component({
  render: () => {
    return `
      <form>
        <div class="form-grid">
          <div class="form-field">
            <label for="firstName">First Name</label>
            <input type="text" name="firstName" placeholder="First Name" />
          </div>
          <div class="form-field">
            <label for="lastName">LastName</label>
            <input type="text" name="lastName" placeholder="LastName" />
          </div>
        </div>
        <div class="form-field">
          <label for="emailAddress">Email Address</label>
          <input type="email" name="emailAddress" placeholder="Email Address" />
        </div>
        <div class="form-field">
          <label for="password">Password</label>
          <input type="password" name="password" placeholder="Password" />
        </div>
        <button type="submit">Sign Up</button>
      </form>
    `;
  },
});

export default Signup;

Arriba, estamos comenzando a construir nuestro /signup página completando el HTML en el render() de nuestro componente función.

Nuestro formulario será simple:solo unas pocas entradas solicitando un nombre y apellido, una dirección de correo electrónico y una contraseña, seguido de un botón de envío.

/ui/pages/signup/index.js

import ui, { accounts } from '@joystick.js/ui';

const Signup = ui.component({
  events: {
    'submit form': (event, component) => {
      event.preventDefault();
      component.validateForm(event.target, {
        rules: {
          firstName: {
            required: true,
          },
          lastName: {
            required: true,
          },
          emailAddress: {
            required: true,
            email: true,
          },
          password: {
            required: true,
            minLength: 6,
          },
        },
        messages: {
          firstName: {
            required: 'First name is required.',
          },
          lastName: {
            required: 'Last name is required.',
          },
          emailAddress: {
            required: 'An email address is required.',
            email: 'Please use a valid email.',
          },
          password: {
            required: 'A password is required.',
            minLength: 'Please use at least six characters.',
          },
        },
      }).then(() => {
        accounts.signup({
          emailAddress: event.target.emailAddress.value,
          password: event.target.password.value,
          metadata: {
            name: {
              first: event.target.firstName.value,
              last: event.target.lastName.value,
            },
          },
        }).then(() => {
          location.pathname = '/dashboard';
        });
      });
    },
  },
  render: () => {
    return `
      <form>
        ...
      </form>
    `;
  },
});

export default Signup;

Ahora para las cosas divertidas. En primer lugar, queremos llamar la atención sobre la parte superior de nuestro archivo. Tenga en cuenta que hemos agregado una importación adicional con nombre para una variable accounts del @joystick.js/ui paquete. Este objeto contiene todas las funciones relacionadas con las cuentas para Joystick (llamadas HTTP a las rutas de cuentas predefinidas en nuestro servidor). Para este componente, usaremos el accounts.signup() función.

Antes de hacer nuestra llamada a esa función, vamos a aprovechar el .validateForm() método que Joystick incluye en nuestro component instancia. Si observamos el código anterior, lo que estamos haciendo aquí es agregar un detector de eventos para el submit evento en el <form></form> estamos renderizando hacia abajo en el render() función.

Dentro de la función asignada al 'submit form' evento:esto es lo que se llamará cada vez que un submit se detecta un evento en nuestro formulario:primero hacemos una llamada a event.preventDefault() para detener el comportamiento predeterminado del navegador de serializar el contenido de nuestro formulario en parámetros de consulta e intentar enviarlos a una URL (en aplicaciones que no son de JavaScript, el contenido de un formulario generalmente se envía como una solicitud HTTP POST a alguna URL definida por el action atributo en el <form></form> elemento).

En cambio, queremos tomar el control total del evento de envío de nuestro formulario y, en su lugar, llamar al accounts.signup() función que insinuamos anteriormente. Sin embargo, antes de hacerlo, queremos usar component.validateForm() (predefinido para nosotros internamente en Joystick en el component instancia a la que podemos acceder en nuestros controladores de eventos como el segundo argumento de la función de devolución de llamada del controlador) para verificar que la entrada del usuario se ajuste a nuestras expectativas.

Aquí, .validateForm() toma dos argumentos:primero, un nodo DOM que representa el <form></form> queremos validar y segundo, un objeto de opciones con dos propiedades, rules y messages . rules contiene las reglas de validación para cada una de nuestras entradas, estableciendo las reglas específicas para cada entrada en una propiedad que coincida con el name atributo de la entrada en nuestro render() función.

A cada propiedad, le pasamos un objeto que contiene las reglas individuales que queremos establecer para cada entrada. Aquí, estamos usando tres reglas:

  1. required que marca la entrada como que requiere un valor.
  2. email que marca la entrada como que requiere una dirección de correo electrónico válida.
  3. minLength que marca la entrada como que requiere un valor de longitud igual al valor pasado (aquí, 6 en el campo de contraseña).

Para mejorar la UX y los comentarios de nuestro formulario, si un usuario no pasa ninguna de las validaciones, el .validateForm() La función mostrará automáticamente un mensaje de error debajo de la entrada con un problema, mostrando uno de los mensajes de error definidos en el messages conjunto de objetos debajo de rules .

Para cada una de las reglas que especificamos en rules , también pasamos un mensaje correspondiente para cada una de esas reglas. Entonces, para el password campo, porque tenemos un required regla y un minLength regla, proporcionamos mensajes de error en caso de que la entrada del usuario no cumpla con esas reglas.

Después de .validateForm() se llama, asumiendo que la entrada del usuario es "buena" y cumple con nuestra validación, el .then() devolución de llamada (.validateForm() nos devuelve una promesa de JavaScript) será despedido. Si la validación falla, el .catch() se activará la devolución de llamada (nos hemos saltado la definición de esto aquí, pero si desea mostrar comentarios adicionales al usuario, como una alerta de brindis, puede hacerlo en el .catch() ).

Dentro del .then() devolución de llamada, finalmente hacemos nuestra llamada a accounts.signup() pasando un objeto con los campos que espera la función. Para nuestras necesidades, estamos pasando tres:

  • emailAddress establecido en el valor de emailAddress campo en nuestro formulario, al que se accede a través de event.target.emailAddress.value donde event.target es nuestro formulario, emailAddress es la entrada con un name atributo igual a emailAddress y value es el valor actual de esa entrada.
  • password establecido en el valor de password campo en nuestro formulario, siguiendo la misma lógica que emailAddress .
  • metadata establecido en un objeto de valores varios que queremos asignar al registro de usuario, aquí, un name para el usuario establecido en un objeto que contiene un first y last propiedad con valores del correspondiente firstName y lastName campos en nuestro formulario.

Similar a .validateForm() , el accounts.signup() la función devuelve una Promesa de JavaScript, así que de nuevo, agregamos un .then() devolución de llamada a esa función que se activará después de que nuestro usuario se haya creado con éxito. En el interior, como sabemos que tenemos un usuario conectado (Joystick configurará automáticamente una cookie en el navegador con un token de inicio de sesión para el usuario), redirigimos al usuario al /dashboard ruta que configuramos anteriormente (location.pathname es un valor establecido en el window.location objeto que, cuando se establece, redirigirá el navegador a esa ruta).

Eso lo hace para registrarse. Las buenas noticias:el resto de nuestras páginas siguen exactamente el mismo patrón, por lo que las recorreremos mucho más rápido.

Cableando la página de inicio de sesión

Pasando al /login página, echemos un vistazo al componente completo y revisemos lo que aprendimos anteriormente:

/ui/pages/login/index.js

import ui, { accounts } from '@joystick.js/ui';

const Login = ui.component({
  events: {
    'submit form': (event, component) => {
      event.preventDefault();
      component.validateForm(event.target, {
        rules: {
          emailAddress: {
            required: true,
            email: true,
          },
          password: {
            required: true,
            minLength: 6,
          },
        },
        messages: {
          emailAddress: {
            required: 'An email address is required.',
            email: 'Please use a valid email.',
          },
          password: {
            required: 'A password is required.',
            minLength: 'Please use at least six characters.',
          },
        },
      }).then(() => {
        accounts.login({
          emailAddress: event.target.emailAddress.value,
          password: event.target.password.value,
        }).then(() => {
          location.pathname = '/dashboard';
        });
      });
    },
  },
  render: () => {
    return `
      <form>
        <div class="form-field">
          <label for="emailAddress">Email Address</label>
          <input type="email" name="emailAddress" placeholder="Email Address" />
        </div>
        <div class="form-field">
          <label for="password">Password <a href="/recover-password">Forget your password?</a></label>
          <input type="password" name="password" placeholder="Password" />
        </div>
        <button type="submit">Log In</button>
      </form>
    `;
  },
});

export default Login;

De nuevo, la misma idea. Arriba importamos ui de @joystick.js/ui , llamando al ui.component() para configurar nuestro componente. Abajo en el render() función, agregamos el marcado HTML para nuestro formulario.

Arriba en el events objeto:recuerde, estos son los eventos DOM que Joystick escuchará automáticamente en nuestro nombre:definimos un oyente para el submit form evento. Así que está claro, cuando definimos un evento en Joystick, usamos el nombre de clave/propiedad del controlador de eventos para describir:

  1. El tipo del evento DOM de JavaScript que estamos escuchando (por ejemplo, submit , click , keyup , etc.).
  2. El selector en el que queremos escuchar el evento (aquí, un form pero también podría ser una clase CSS como .login-form ).

A esa clave/nombre de propiedad, le asignamos la función a llamar cada vez que ocurra ese evento. Dentro, nos aseguramos de llamar al event.preventDefault() para asegurarse de que el navegador no realizar el comportamiento predeterminado en el navegador de serializar nuestros valores de formulario e intentar HTTP POST en el action atributo en nuestro formulario (que no existe).

A continuación, volvemos a traer nuestro .validateForm() función que se nos entrega automáticamente como parte de @joystick.js/ui a través del component instancia. A esa función, tal como vimos antes, le pasamos el elemento DOM para nuestro formulario (aquí, simplemente extrayendo el target propiedad del evento DOM original en el navegador), seguido de un objeto de opciones que describe las reglas que queremos validar nuestro formulario por y los mensajes de error que se mostrarán si la entrada del usuario falla en la validación.

Porque esperamos .validateForm() para devolver una promesa de JavaScript, encadenamos un .then() devolución de llamada al final donde podemos llamar al accounts.login() función (un hermano del accounts.signup() función que usamos anteriormente en el accounts objeto importado de @joystick.js/ui ).

A esa función, en un objeto, desde el event.target representando nuestro formulario, pasamos los valores para el emailAddress campo (recuerde, esto se asigna a la entrada con ese name atributo) y el password campo.

Suponiendo que la dirección de correo electrónico y la contraseña de nuestro usuario coincidan con un usuario, accounts.login() devolverá una promesa de JavaScript que encadenamos un .then() devolución de llamada a para manejar el estado de éxito. En esa devolución de llamada, tal como lo hicimos en el /signup página, redirigimos al /dashboard ruta configurando el pathname atributo en el window de location objeto (nuevamente, no hemos definido ni importado esto, esto existe globalmente en el navegador).

Eso lo hace por el /login página. Ahora, pasemos a la recuperación y restablecimiento de la contraseña.

Conexión de la página de recuperación de contraseña

Para restablecer la contraseña de un usuario, debemos generar un intento/token de restablecimiento y agregarlo a su registro de usuario en la base de datos. Para hacerlo, crearemos una página de "recuperación de contraseña" donde un usuario puede ingresar su correo electrónico para iniciar el intento de restablecimiento.

La buena noticia:todo lo que aprendimos anteriormente también se aplica aquí. Echemos un vistazo al componente completo ya que este no tiene mucho código:

/ui/pages/recoverPassword/index.js

import ui, { accounts } from '@joystick.js/ui';

const RecoverPassword = ui.component({
  events: {
    'submit form': (event, component) => {
      event.preventDefault();
      component.validateForm(event.target, {
        rules: {
          emailAddress: {
            required: true,
            email: true,
          },
        },
        messages: {
          emailAddress: {
            required: 'An email address is required.',
            email: 'Please use a valid email.',
          },
        },
      }).then(() => {
        accounts.recoverPassword({
          emailAddress: event.target.emailAddress.value,
        }).then(() => {
          window.alert(`Check your email at ${event.target.emailAddress.value} for a reset link.`);
        });
      });
    },
  },
  render: () => {
    return `
      <form>
        <div class="form-field">
          <label for="emailAddress">Email Address</label>
          <input type="email" name="emailAddress" placeholder="Email Address" />
        </div>
        <button type="submit">Reset Password</button>
      </form>
    `;
  },
});

export default RecoverPassword;

Nuevamente, aunque puede ser aburrido, queremos enfatizar la importancia de seguir un patrón. Aquí, seguimos exactamente los mismos pasos que vimos anteriormente, renderizando nuestro HTML, agregando un detector de eventos, validando nuestro formulario y luego realizando la acción relacionada (en este caso, llamando a accounts.recoverPassword() y pasando un emailAddress ).

Falta un componente más (que nos presenta algunas funciones nuevas):restablecer la contraseña.

Conexión de la página de restablecimiento de contraseña

Después de que se haya enviado un intento de recuperación de contraseña utilizando el /recover-password página que conectamos arriba, si su config.smtp la configuración está presente en su settings.<env>.json archivo en la raíz de su proyecto, Joystick intentará enviar un correo electrónico de restablecimiento de contraseña. En desarrollo, Joystick cerrará automáticamente una URL de restablecimiento de contraseña en su terminal (donde inició la aplicación Joystick) para realizar pruebas.

Esa URL va a /reset-password/:token donde :token es un token generado dinámicamente como joXUGGscutZcvanJQ8Ao9qABjZkGUdSB que se asigna al passwordResetTokens matriz en el usuario en la base de datos (correspondiente a la dirección de correo electrónico ingresada en la página de recuperación).

/ui/pages/resetPassword/index.js

import ui, { accounts } from '@joystick.js/ui';

const ResetPassword = ui.component({
  events: {
    'submit form': (event, component) => {
      event.preventDefault();
      component.validateForm(event.target, {
        rules: {
          newPassword: {
            required: true,
            minLength: 6,
          },
          repeatNewPassword: {
            required: true,
            minLength: 6,
            equals: event.target.newPassword.value,
          },
        },
        messages: {
          newPassword: {
            required: 'Must enter a new password.',
            minLength: 'Password must be at least six characters.',
          },
          repeatNewPassword: {
            required: 'Must repeat new password.',
            minLength: 'Password must be at least six characters.',
            equals: 'Passwords must match.',
          },
        },
      }).then(() => {
        accounts.resetPassword({
          token: component.url.params.token,
          password: event.target.newPassword.value,
        }).then(() => {
          window.alert(`Password reset, logging you back in...`);
          location.pathname = '/dashboard';
        });
      });
    },
  },
  render: () => {
    return `
      <form>
        <div class="form-field">
          <label for="newPassword">New Password</label>
          <input type="password" name="newPassword" placeholder="New Password" />
        </div>
        <div class="form-field">
          <label for="repeatNewPassword">Repeat New Password</label>
          <input type="password" name="repeatNewPassword" placeholder="Repeat New Password" />
        </div>
        <button type="submit">Reset Password</button>
      </form>
    `;
  },
});

export default ResetPassword;

Concepto similar con algunas diferencias menores. La forma en que representamos el HTML para nuestro componente y el uso de un detector de eventos es la misma, pero mire de cerca dos cosas:el rules en .validateForm() y que estamos pasando a accounts.resetPassword() .

Para las reglas, estamos usando una regla impar, equals . Tenga en cuenta que esto se establece igual al valor de la entrada con un name atributo igual a newPassword . Esto se debe a que en esta página, para restablecer la contraseña de alguien, queremos confirmar que haya ingresado correctamente su nueva contraseña antes cambiándolo.

En segundo lugar, en nuestra llamada a accounts.resetPassword() observe que estamos pasando un token campo que se establece en component.url.params.token . En Joystick, la información sobre la URL actual está disponible en el url objeto en la instancia del componente. Aquí, decimos "Danos el valor actual de :token parámetro en la URL".

Este token se asigna, hipotéticamente, a algún usuario en la base de datos a través de su passwordResetTokens formación. Cuando llamamos a accounts.resetPassword() , asumiendo que el token es válido, la contraseña del usuario se actualiza, el token caduca (eliminado de su passwordResetTokens matriz) y el usuario inicia sesión automáticamente.

Insinuamos esto en el .then() devolución de llamada para accounts.resetPassword() alertando al usuario sobre el inicio de sesión automático y luego redirigirlo a /dashboard suponiendo que tenemos un token de usuario conectado en las cookies del navegador (indicado allí como joystickLoginToken ).

Adición de rutas autenticadas y públicas

Si bien tenemos todas nuestras páginas de cuentas configuradas, antes de terminar, es importante echar un vistazo a la creación de rutas autenticadas frente a rutas públicas en Joystick. Una "ruta autenticada" es aquella que requiere que un usuario haya iniciado sesión para verla, mientras que una "ruta pública" es aquella que no requiere un usuario registrado para verlo.

En Joystick, tenemos dos métodos auxiliares para administrar este proceso en el servidor:.ifLoggedIn() y .ifNotLoggedIn() , ambos asignados al req.context objeto de las solicitudes HTTP entrantes en nuestras rutas. Volviendo al servidor, veamos cómo funcionan:

/index.servidor.js

import node from "@joystick.js/node";
import api from "./api";

node.app({
  api,
  routes: {
    "/dashboard": (req, res) => {
      req.context.ifNotLoggedIn('/login', () => {
        res.render("ui/pages/dashboard/index.js", {
          layout: "ui/layouts/app/index.js",
        });
      });
    },
    "/signup": (req, res) => {
      req.context.ifLoggedIn('/dashboard', () => {
        res.render("ui/pages/signup/index.js", {
          layout: "ui/layouts/app/index.js",
        });
      });
    },
    "/login": (req, res) => {
      req.context.ifLoggedIn('/dashboard', () => {
        res.render("ui/pages/login/index.js", {
          layout: "ui/layouts/app/index.js",
        });
      });
    },
    "/recover-password": (req, res) => {
      req.context.ifLoggedIn('/dashboard', () => {
        res.render("ui/pages/recoverPassword/index.js", {
          layout: "ui/layouts/app/index.js",
        });
      });
    },
    "/reset-password/:token": (req, res) => {
      req.context.ifLoggedIn('/dashboard', () => {
        res.render("ui/pages/resetPassword/index.js", {
          layout: "ui/layouts/app/index.js",
        });
      });
    },
    "*": (req, res) => {
      res.render("ui/pages/error/index.js", {
        layout: "ui/layouts/app/index.js",
        props: {
          statusCode: 404,
        },
      });
    },
  },
});

De vuelta al interior de nuestro index.server.js archivo y mirando nuestras rutas, podemos ver estas dos funciones en juego. Están diseñados para leer como una oración.

"Si el usuario no iniciado sesión, vaya a esta ruta, de lo contrario, ejecute esta función" o "si el usuario es conectado, vaya a esta ruta, de lo contrario, ejecute esta función". La idea aquí es que algunas rutas en nuestra aplicación requerirán un usuario y otras no. Esto tiene un doble propósito:reforzar la seguridad de nuestra aplicación y mejorar la experiencia del usuario. (UX).

Por ejemplo, si llega al /dashboard la ruta requiere un usuario (tal vez porque en nuestra aplicación el tablero carga datos privados), no queremos que el usuario pueda acceder a esa ruta si no ha iniciado sesión. Mirando eso a través de una lente UX, redirigiendo aleja al usuario de lo que intenta acceder, le comunicamos que no ha cumplido con los requisitos necesarios para ver esa página (y, con suerte, la redirección a una página como /login comunica que necesitan iniciar sesión para llegar allí).

Por el contrario, cuando se trata de .ifLoggedIn() , queremos comunicar a un usuario que ha iniciado sesión que no puede volver a las páginas destinadas únicamente a cerrar usuarios Se trata menos de seguridad y más de UX y de evitar que surjan errores complejos.

Deberias hacer eso. Hagamos una prueba de manejo y veamos cómo funciona todo.

Terminando

En este tutorial, aprendimos cómo aprovechar el sistema de cuentas integrado de Joystick para conectar un flujo de cuentas para nuestra aplicación. Aprendimos cómo registrar nuevos usuarios, iniciar sesión en usuarios existentes e implementar un flujo de trabajo de recuperación de contraseña para usuarios existentes que olvidan su contraseña. También aprendimos cómo crear rutas "protegidas" usando el .ifLoggedIn() integrado de Joystick. y .ifNotLoggedIn() funciones definidas en la solicitud HTTP para ayudarnos a mejorar la seguridad y la experiencia del usuario.