NestJS avanzado: acceso al usuario actual

nestjs

NestJS avanzado: cómo tener acceso al usuario actual en cada servicio sin alcance de solicitud

NestJS avanzado — Si crea API con NestJS con frecuencia, tarde o temprano se encontrará con una situación en la que necesitará el usuario conectado actualmente, el objeto de solicitud en sí o los permisos que tiene el usuario en un método de servicio en particular.


Si busca en Google sobre NestJS avanzado, encontrará muchos artículos que indican que esto solo es posible de dos maneras:

1. Puede inyectar la solicitud en la acción del controlador y luego arrastrarla a través de cada función como esta:

@Get()
getAction(@Body() body: SuperInterface, @Req() req: Request) {
this.myService.performAction(body, req);
}

Pero esto se vuelve aún más difícil cuando su método performAction se llama a sí mismo otros métodos que llaman a otros métodos y así sucesivamente. Luego, debe pasar el objeto de solicitud a través de todas las funciones, lo que se vuelve poco práctico.

2. Puede configurar el alcance de la inyección del servicio en Scope.REQUEST

Si establece el ámbito de inyección del servicio en Scope.REQUEST, puede inyectar la solicitud actual (y, por lo tanto, el usuario u otros datos relacionados con la solicitud) al constructor.

@Injectable({ scope: Scope.REQUEST })
export class CatsService {
constructor(@Inject(REQUEST) private request: Request) {}
}

Pero con este método, ralentizará cada solicitud y toda su aplicación drásticamente. Porque todos los servicios deben crearse sobre la marcha para cada solicitud.


💡TL;DR — ¡Hay una solución para esto!

Existe una API de nodo ampliamente desconocida llamada AsyncLocalStorage que es perfecta para esta situación y es muy fácil de implementar.

Creará y administrará tiendas separadas para cada contexto asincrónico en el que nos encontremos. Es un poco difícil de entender eso, pero intentaré explicarlo lo más fácil posible:

Crea AsyncLocalStorage una vez (!) y luego puede llamar a los métodos para obtener y configurar la tienda dentro de un contexto asíncrono como una Promise o una  async function.

Cuando llame al captador, solo obtendrá la tienda que se configuró en el mismo contexto. NodeJS hará un seguimiento de qué tienda pertenece a qué ejecución de llamada.

Encuentre más sobre esto aquí: https://nodejs.org/api/async_context.html

Pero ahora implementemos algo…


Primero, debe crear un archivo que cree AsyncLocalStorage para nuestro propósito.

Para fines de demostración, creé solo una interfaz de User muy simple.

// user.storage.ts
import { AsyncLocalStorage } from 'async_hooks';

export interface User {
id: string;
}

export const UserStorage = {
storage: new AsyncLocalStorage<User>(),
get() {
return this.storage.getStore();
},
set(user: User) {
return this.storage.enterWith(user);
},
};

En segundo lugar, debe insertar el usuario en nuestra tienda.

Como estoy usando @nestjs/passport con una estrategia JWT, tengo un servicio JWTStrategy extends PassportStrategy(Strategy) con una función de validate():

async validate(payload: JwtPayload) {
const user = await this.userService
.getUserById(payload.id)
.catch(() => {
throw new UnauthorizedException();
});
if (!user) {
throw new UnauthorizedException();
}
UserStorage.set(user);
return user;
}

Como puede ver, estoy llamando a UserStorage.set(user) que configurará al usuario para el contexto asíncrono actual.

Tercero, puede llevar al usuario donde quiera.

Y, por último, puede llamar a UserStorage.get() en cada función que desee y obtendrá el usuario para esta solicitud específica, porque normalmente la función de autenticación se llama antes que la acción del controlador.

Por ejemplo nuestro servicio desde el principio con nuestro método  performAction:

@Injectable()
class MyService {
async performAction(body: SuperInterface) {
const user = UserStorage.get();
// ... do something with the user
} 
}

Resumen

Con AsyncLocalStorage puede crear almacenamientos muy eficaces para solicitar datos relacionados. Además del objeto de usuario, podría pensar en:

  • El propio objeto Request. Por ejemplo, para fines de registro. Puede crear una identificación única para cada solicitud y luego agregar esta identificación a cada registro. Entonces podría asignar cada registro a la solicitud que lo activa.

 

  • Un conjunto de permisos. Puede obtener los permisos que tiene un usuario de una base de datos redis una vez al comienzo de la solicitud y luego verificar fácilmente si el usuario puede hacer lo que está tratando de hacer.

Gracias por llegar hasta aquí, si encuentras esto útil no olvides aplaudir 👍🏼suscribirse para recibir más contenido.

Si le interesa, puede echar un vistazo a algunos de los otros artículos que he escrito recientemente sobre AWS y Laravel:

Recent Post