我正在创建一个多公司应用程序,登录用户只能访问他的内容。目前我不想在这个应用程序中实现多租户。
我创建了一个装饰器来获取当前登录的用户:
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
并在我的控制器操作上使用此装饰器来获取用户的 companyId 并将此 ID 传递给服务。
@Get()
async getAll(
@CurrentUser()
currentUser,
) {
return this.categoryService.getAll(currentUser.companyId);
}
现在,在服务中我可以访问companyId并可以访问他的内容:
async getAll(companyId: string) {
const categories = await this.prisma.category.findMany({
where: { companyId: companyId },
});
return categories;
}
最大的问题是我必须在每个控制器、服务等中重复这个过程。
有没有更简单的方法来做到这一点?我可以在服务级别上使用什么吗?我该如何解决这个问题?
您好,最简单的方法之一是
userGuard
,您需要jwt token
才能使用它。现在你可能想知道什么是useguards以及它们是如何使用的。我想给你一个真实的例子,
import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy,ExtractJwt } from "passport-jwt";
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy,'jwt') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'super-secret-cat',
});
}
async validate(payload: any) {
return payload
}
}
这是一个简单的 JWT 防护。首先检查 jwt 登录,然后使用
@UseGuards(AuthGuard("jwt")
您验证了用户的身份 了解更多信息 https://docs.nestjs.com/guards
不确定你到底想要实现什么,但我会这样做:
/* File: CategoryService.ts */
import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
@Injectable({ scope: Scope.REQUEST })
export class CategoryService {
// declare your variables/properties
constructor(@Inject(REQUEST) private request: Request) {
}
// get current user
getCurrentUserCompanyId() {
return this.request.user['companyId'];
}
async getAll() {
const categories = await this.prisma.category.findMany({
where: { companyId: this.getCurrentUserCompanyId() },
});
return categories;
}
}
/* File: category.controller.ts */
@Controller('category')
export class CategoryController {
constructor(private categoryService: CategoryService) {}
@Get()
async getAll() {
return this.categoryService.getAll();
}
}
我在同一个地方。经过几个小时的研究,终于找到了这个用例的解决方案。 (尚未在生产环境中运行它看到结果。)
基本上,主要技巧是使用这个库来访问连接的用户数据。 一旦这一部分完成,为了确保没有人(包括开发人员)访问意外数据,我有 Prisma 中间件(它也具有软删除功能,这是题外话,但不想排除它,因为我假设我们都做吧。)
这是我的 PrismaClient 代码:
import { Injectable, OnModuleInit } from '@nestjs/common';
import { Prisma, PrismaClient } from '@prisma/client';
import { __CLS_CONN_USER, __DEFAULT_COMPANY_CODE } from '../../utils/constants';
import { ClsService } from 'nestjs-cls';
import { UserEntity } from '../users/entities/user.entity';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
constructor(
private readonly cls: ClsService
) {}
async onModuleInit() {
await this.$connect();
this.$use(this.softDeleteAndCompanyCodeMiddleware);
//For the future: use $extends => https://github.com/prisma/prisma/issues/18628
}
getConnUserCompanyCode(): string {
let companyCode = __DEFAULT_COMPANY_CODE;
const connUser = this.cls.get(__CLS_CONN_USER) as UserEntity;
if (connUser) {
companyCode = connUser.companyCode;
}
return companyCode;
}
softDeleteAndCompanyCodeMiddleware: Prisma.Middleware = async (params, next) => {
if (params.action === 'findUnique' || params.action === 'findFirst' || params.action === 'count') {
const obj = {
...params,
action: params.action,
args: {
...params.args,
where: {
...params.args?.where,
isDeleted: false,//soft-delete, bring only undeleted
},
},
}
//do not apply for User Login action.
if (params.model !== 'User')
obj.args.where.companyCode = this.getConnUserCompanyCode();//bring only records within the same company
return next(obj);
}
if (params.action === 'findMany') {
return next({
...params,
args: {
...params.args,
where: {
...params.args?.where,
isDeleted: false,//soft-delete, bring only undeleted
companyCode: this.getConnUserCompanyCode()//bring only records within the same company
},
},
});
}
if (params.action === 'create') {
return next({
...params,
args: {
data: {
...params.args.data,
isDeleted: false,//never allow the developers to create a record as deleted.
companyCode: this.getConnUserCompanyCode()//make sure no recorded is created for a different company
}
},
});
}
if (params.action === 'update') {
return next({
...params,
args: {
data: {
...params.args.data,
companyCode: undefined//never let users to update companyCode field
},
where: {
...params.args?.where,
isDeleted: false,
companyCode: this.getConnUserCompanyCode()//make sure to only update a record with the same companyCode
},
},
});
}
if (params.action === 'delete') {
return next({
...params,
action: 'update',
args: {
...params.args,
data: {
isDeleted: true,
},
where: {
...params.args.where,
isDeleted: false,
companyCode: this.getConnUserCompanyCode()//allow only to soft-delete a record with the same companyCode
},
},
});
}
return next(params);
};