'get Forbidden resource even when the requested user is admin

Middleware is used to protect the resolver. The middleware checks the role of user and despite the right role of the requested user I cannot access listUser query. I get following response

"errors": [
    {
      "message": "Forbidden resource",
      "extensions": {
        "code": "FORBIDDEN",
        "response": {
          "statusCode": 403,
          "message": "Forbidden resource",
          "error": "Forbidden"
        }
      }
    }
  ],
  "data": null
}

Here is my code

model User {
  id            String           @id @default(uuid())
  firstName     String
  lastName      String
  email         String           @unique
  username      String           @unique
  password      String
  confirm       Boolean          @default(false)
  isValid       Boolean          @default(false) @map("is_valid")
  isSuperuser   Boolean          @default(false)
  userRoles     UserRole[]

  @@unique([id, email])
}

model Role {
  id        String      @id @default(uuid())
  name      String      @unique
  createdAt DateTime?   @db.Timestamp()
  updatedAt DateTime?   @updatedAt @db.Timestamp()
  userRoles UserRole[]
}

model UserRole {
  id        String    @id @default(uuid())
  userId    String
  user      User      @relation(fields: [userId], references: [id])
  roleId    String
  role      Role      @relation(fields: [roleId], references: [id])
  createdAt DateTime? @default(now())
  updatedAt DateTime? @updatedAt @db.Timestamp()
}

@Injectable()
export class UserMiddleware implements NestMiddleware {
  constructor(
    private readonly prismaService: PrismaService,
    private readonly validationService: ValidationService,
  ) {}

  async use(req: any, res: any, next: () => void) {
    if (req.headers.authorization) {
      const { userId } = jwt.decode(
        String(req.headers.authorization).split(/ /g)[1],
      ) as JWT;
      const user = await this.prismaService.user.findFirst({
        where: { id: userId },
        include: { userRoles: true },
      });
      if (user && !user.confirm)
        throw new ForbiddenException('User not confirmed');

      if (user) {
        delete user.password;
        req.user = user;
        req.user.isAdmin = await this.validationService.isAdmin(userId);
      }
    }
    next();
  }
}

@Injectable()
export class ValidationService {
  constructor(private readonly prisma: PrismaService) {}

  async isAdmin(userId: string): Promise<boolean> {
    const u = await this.prisma.role.findUnique({
      where: { name: Role.Admin },
    });
    return await this.prisma.userRole
      .count({
        // could not find a way to access directly through role name instead of id
        where: { userId, roleId: u.id },
      })
      .then((count) => {
        return count ? true : false;
      });
  }
}

  @Query(() => UserPaginated)
  @Roles(Role.Admin)
  async listUsers(
    @Args('paginate', { nullable: true, defaultValue: { skip: 0, take: 50 } })
    paginate: PaginationArgs,
    @Args('order', {
      nullable: true,
      defaultValue: { orderBy: 'username', direction: 'desc' },
    })
    order: OrderListUsers,
    @Args('filter', { nullable: true })
    filter: FilterListUsers,
  ) {
    return await this.userService.list(paginate, order, filter);
  }

Why am I not able to access listUser with role as Admin ?



Solution 1:[1]

I think your ValidationService has some problems. Please try this. But i should say that, this is not the perfect solution. Your table structure and prisma requests needs some improvements.

@Injectable()
export class ValidationService {
  constructor(private readonly prisma: PrismaService) {}

  async isAdmin(userId: string): Promise<boolean> {
    const u = await this.prisma.role.findUnique({
      where: { name: Role.Admin },
    });
    const userRoleCount = await this.prisma.userRole
      .count({
        where: { userId, roleId: u.id },
      });
    return userRoleCount > 0;
  }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Dagistan