8.7-bob: User–Role–Auth, RBAC (ruxsatlarni boshqarish)
8-QISM — NestJS (chuqur) · 7-mavzu
1. Kirish va motivatsiya
Guards va enhancer mexanizmlarini 8.6-bob bildik. Endi ularni ishlatib, NestJS'da to'liq RBAC (Role-Based Access Control — rollarga asoslangan ruxsat) tizimini quramiz. 5.17-bobda RBAC'ni Express'da nazariy/amaliy ko'rdik; endi NestJS'da — guard, custom dekorator, ownership, va dinamik ruxsatlar bilan — professional darajada. Bu — har korporativ ilovaning markaziy qismi (admin, moderator, user — kim nimaga haqli).
5.17'dagi ikki savol takrorlanadi: authentication (8.9 — kim siz?) va authorization (bu bob — nimaga haqlisiz?). NestJS'da authorization — RolesGuard (8.6: 2.4) + @Roles() custom dekorator (8.6: 2.9) + Reflector (metadata o'qish — 8.6: 2.7). Bu — 5.17'dagi qo'lda middleware'ning NestJS'dagi deklarativ, toza versiyasi: @Roles("admin") yozasiz — guard avtomatik tekshiradi.
Bu bob promptdagidan ko'proq beradi: oddiy rol-asosli RBAC (rol ruxsat), permission-asosli (aniq ruxsatlar — 5.17: 2.10), ownership (o'z resursini — 5.17: 2.8, IDOR himoyasi — 14), va CASL (dinamik, attribut-asosli ruxsat — promptda yo'q, lekin professional NestJS RBAC'ning standarti). Bu bob: User-Role entity, RolesGuard, permission guard, ownership, va CASL — chuqur. Bu — auth 8.9-bob bilan birga ishlaydi.
O'xshatish (RBAC): RBAC — kompaniyadagi lavozimlar va kalitlar 5.17-bob. Xodimga lavozim beriladi (rol — admin/moderator/user); har lavozim muayyan xonalar kalitini oladi (ruxsatlar). NestJS'da
@Roles("admin")— "bu eshik faqat admin kalitini qabul qiladi"; RolesGuard — eshik qulfini tekshiruvchi. Ownership — "o'z kabinetingizga kirasiz, boshqaning kabinetiga yo'q" (rol bir xil bo'lsa ham — 14). CASL — aqlli qulf ("o'z hujjatingizni tahrirlaysiz, lekin boshqanikini faqat o'qiysiz").
Nega muhim?
- Korporativ markaz — admin/moderator/user — har ilovada.
- Xavfsizlik (14) — kim nimaga haqli; ruxsatsiz amal — bloklanadi.
- Auth davomi (8.9) — authentication'dan keyin authorization.
- CASL — murakkab, dinamik ruxsat (professional NestJS).
2. Nazariya — chuqur tushuntirish
2.1. Authentication vs Authorization (NestJS'da — 5.17 takrori)
Authentication 8.9-bob: KIM siz? AuthGuard (JWT — req.user)
Authorization (bu bob): NIMAGA haqlisiz? RolesGuard (rol/ruxsat)
Tartib (8.6: 2.11): AuthGuard RolesGuard
(avval kim ekanini aniqla, keyin ruxsatni tekshir)Authorization (bu bob) — authentication'dan (8.9) keyin. AuthGuard
req.userni qo'yadi 8.9-bob, RolesGuard uning rolini tekshiradi (5.17: 2.1). Guard tartibi muhim:@UseGuards(AuthGuard, RolesGuard)(8.6: 2.11).
2.2. User-Role modeli (entity)
// Oddiy: rol — enum (user entity'da)
@Entity()
export class User {
@PrimaryGeneratedColumn() id: number;
@Column({ type: "enum", enum: ["user", "moderator", "admin"], default: "user" })
rol: string; // rol (5.17: 2.4)
} Murakkab: alohida Role/Permission entity (dinamik — 2.9)
User N:M Role N:M Permission (DB'dan boshqariladi)Rol modeli: oddiy —
rolenum (user entity'da — ko'p loyiha uchun yetadi). Murakkab — alohidaRole+Permissionentity (N:M — dinamik, admin paneldan boshqariladi — 2.9). Boshlash uchun enum; o'sgan sari permission-asosli 2.7-bob.
2.2a. User modul: entity, service, module (to'liq)
RBAC'ning poydevori — User entity va uni boshqaradigan service. Rol shu entity'da yashaydi 2.2-bob, auth 8.9-bob esa shu user'ni topib, token'ga rolni joylaydi. To'liq user modulini quramiz:
// user.entity.ts (8.3 — TypeORM entity)
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
import { Rol } from "./rol.enum"; // (Misol 1)
@Entity("users")
export class User {
@PrimaryGeneratedColumn() id: number;
@Column({ unique: true }) email: string; // login uchun (8.9)
@Column({ select: false }) parolHash: string; // select:false — so'rovlarda kelmaydi (14)
@Column({ type: "enum", enum: Rol, default: Rol.USER })
rol: Rol; // RBAC markazi (2.2)
@OneToMany(() => Order, (order) => order.user)
buyurtmalar: Order[]; // ownership uchun (2.6)
}// users.service.ts — user CRUD + rol boshqaruvi
import { Injectable, NotFoundException, ConflictException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
@Injectable()
export class UsersService {
constructor(@InjectRepository(User) private repo: Repository<User>) {}
// auth 8.9-bob — email bo'yicha topish (parolHash bilan — addSelect)
async emailBoyicha(email: string): Promise<User | null> {
return this.repo
.createQueryBuilder("u")
.addSelect("u.parolHash") // select:false ni ochamiz (14)
.where("u.email = :email", { email })
.getOne();
}
async bitta(id: number): Promise<User> {
const user = await this.repo.findOne({ where: { id } });
if (!user) throw new NotFoundException("Foydalanuvchi topilmadi");
return user;
}
async yarat(email: string, parolHash: string): Promise<User> {
const bor = await this.repo.findOne({ where: { email } });
if (bor) throw new ConflictException("Bu email band"); // 409
const user = this.repo.create({ email, parolHash }); // rol — default USER (2.2)
return this.repo.save(user);
}
// rol tayinlash — faqat admin chaqiradi (Misol 9)
async rolTayinla(id: number, rol: Rol): Promise<User> {
const user = await this.bitta(id);
user.rol = rol;
return this.repo.save(user);
}
}// users.module.ts — modul (8.2)
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
@Module({
imports: [TypeOrmModule.forFeature([User])], // repository DI (8.3)
providers: [UsersService],
exports: [UsersService], // AuthModule 8.9-bob ishlatadi
})
export class UsersModule {}User modul — RBAC'ning poydevori. Entity (
rolustuni — 2.2;parolHashselect:falsebilan yashirin — 14;buyurtmalarrelation — ownership uchun 2.6). Service —emailBoyicha(auth login — 8.9,parolHash'niaddSelectbilan ochadi),yarat,rolTayinla(admin — Misol 9). Module —UsersService'niexportqiladi (AuthModule— 8.9 — import qilib ishlatadi). Bu modul — auth 8.9-bob va RBAC (bu bob) uchun umumiy asos.
2.3. @Roles dekorator + RolesGuard (asosiy — 8.6 takrori)
// roles.decorator.ts (8.6: 2.9)
export const Roles = (...roles: string[]) => SetMetadata("roles", roles);
// roles.guard.ts (8.6: 2.7)
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const kerakli = this.reflector.getAllAndOverride<string[]>("roles", [
context.getHandler(), context.getClass(),
]);
if (!kerakli) return true; // rol talab qilinmagan
const { user } = context.switchToHttp().getRequest();
return kerakli.includes(user.rol); // RBAC (5.17)
}
}
// Ishlatish
@UseGuards(AuthGuard, RolesGuard)
@Roles("admin")
@Delete(":id")
ochir() {}Rol-asosli RBAC (8.6'da ko'rdik):
@Roles("admin")(metadata) +RolesGuard(Reflector o'qiydi).AuthGuard, RolesGuard(tartib — auth birinchi — 2.1). Bu — eng oddiy, keng ishlatiladigan RBAC. Ko'p loyiha uchun yetadi.
getAllAndOverridenima uchun? (8.6: 2.7) — metadata ikki joyda bo'lishi mumkin: handler'da (metod ustidagi@Roles) va class'da (controller ustidagi@Roles).getAllAndOverride<T>(key, [getHandler(), getClass()])— massivni tartib bilan tekshiradi va birinchi topilganini qaytaradi (handler class'ni ustidan bosadi — override). Ya'ni: controller@Roles(ADMIN)bo'lsa, lekin bitta metodda@Roles(USER)bo'lsa — o'sha metoddaUSERamal qiladi. Buning muqobili —getAllAndMerge(ikkalasini birlashtiradi, override qilmaydi) yoki oddiyget(faqat bitta target'dan o'qiydi — 2.4, 2.5'da). Rollar uchungetAllAndOverridemantiqiy: aniqroq (handler) darajadagi qoida umumiyni (class) bekor qiladi.
2.4. Rol iyerarxiyasi (NestJS'da — 5.17: 2.7)
const ROL_DARAJA = { user: 1, moderator: 2, admin: 3, superadmin: 4 };
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const minRol = this.reflector.get<string>("minRole", context.getHandler());
if (!minRol) return true;
const { user } = context.switchToHttp().getRequest();
return ROL_DARAJA[user.rol] >= ROL_DARAJA[minRol]; // daraja (5.17: 2.7)
}
}
// @MinRole("moderator") moderator va yuqori (admin, superadmin) o'tadiRol iyerarxiyasi (5.17: 2.7) — yuqori rol quyi ruxsatni oladi (admin > moderator > user). NestJS'da — daraja taqqoslash guard'da.
@MinRole("moderator")— moderator va yuqori. Tekis rol (faqat aniq rol) yoki iyerarxik — loyihaga qarab.
2.5. Permission-asosli RBAC (aniq ruxsatlar — 5.17: 2.10)
// Rol ruxsatlar (5.17: 2.10)
const ROL_RUXSAT: Record<string, string[]> = {
user: ["buyurtma:yaratish", "profil:o'qish"],
moderator: ["sharh:o'chirish", "buyurtma:o'qish"],
admin: ["*"], // hammasi
};
export const Permissions = (...p: string[]) => SetMetadata("permissions", p);
@Injectable()
export class PermissionsGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const kerakli = this.reflector.get<string[]>("permissions", context.getHandler());
if (!kerakli) return true;
const { user } = context.switchToHttp().getRequest();
const ruxsatlar = ROL_RUXSAT[user.rol] || [];
return ruxsatlar.includes("*") || kerakli.every((p) => ruxsatlar.includes(p));
}
}
// @Permissions("sharh:o'chirish")Permission-asosli (5.17: 2.10) — rol emas, aniq ruxsat (
@Permissions("sharh:o'chirish")). Moslashuvchanroq: yangi ruxsat qo'shish oson (rolga permission qo'shasiz, kodga tegmaysiz). Murakkabroq, lekin nozik. Katta loyihada afzal.
2.6. Ownership (o'z resursi — IDOR himoyasi — 14)
Rol yetarli emas: foydalanuvchi o'z resursini o'zgartirishi mumkin, boshqaning emas (5.17: 2.8):
// Ownership service'da tekshiriladi (guard'da resurs yo'q)
@Injectable()
export class OrdersService {
async ochir(orderId: number, user: User): Promise<void> {
const order = await this.repo.findOne({ where: { id: orderId } });
if (!order) throw new NotFoundException();
// OWNERSHIP: o'ziniki yoki admin (5.17: 2.8, 14)
if (order.userId !== user.id && user.rol !== "admin") {
throw new ForbiddenException("Bu buyurtma sizniki emas"); // 403 (IDOR — 14)
}
await this.repo.delete(orderId);
}
}Ownership (5.17: 2.8, 14): rol bir xil bo'lsa ham, foydalanuvchi boshqaning resursini o'zgartira olmasligi kerak (IDOR hujumi — 14). Guard'da resurs yo'q (DB so'rovsiz) service'da tekshiriladi (
order.userId === user.id). Yoki ownership guard (DB bilan — 2.7). Eng ko'p e'tibordan chetda qoladigan, lekin muhim.
2.7. Ownership guard (qayta ishlatiladigan)
// Ownership guard (DB bilan — 5.17: Misol 4)
@Injectable()
export class OwnershipGuard implements CanActivate {
constructor(private dataSource: DataSource, private reflector: Reflector) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const entityClass = this.reflector.get("ownerEntity", context.getHandler());
const request = context.switchToHttp().getRequest();
const { user } = request;
const id = request.params.id;
if (user.rol === "admin") return true; // admin — hammasi
const resurs = await this.dataSource.getRepository(entityClass).findOne({ where: { id } });
if (!resurs) throw new NotFoundException();
if (resurs.userId !== user.id) throw new ForbiddenException("Ruxsat yo'q"); // (14)
return true;
}
}
// @CheckOwnership(Order) @UseGuards(AuthGuard, OwnershipGuard)Ownership guard — qayta ishlatiladigan (DB'dan resurs olib, egasini tekshiradi). Lekin guard'da DB so'rovi (qo'shimcha) — ba'zan service'da tekshirish 2.6-bob soddaroq (resurs baribir kerak). Tanlov: takroriy ownership guard; bir martalik service.
2.8. CASL — dinamik, attribut-asosli (promptda yo'q — professional)
CASL — NestJS'da murakkab ruxsatning standart kutubxonasi (docs.nestjs.com — authorization):
CASL nima:
- "ability" — kim nima qila oladi (can/cannot)
- Attribut-asosli: shartlar bilan ("o'z postini tahrirlaydi")
- Dinamik: runtime'da baholanadi (resurs ma'lumotiga qarab)
- RBAC + ownership + ABAC — hammasi bir joyda
RBAC vs CASL:
RBAC — sodda (rol ruxsat); katta loyihada cheklangan
CASL — moslashuvchan (shart, ownership, attribut); murakkabroqCASL (promptda yo'q, lekin professional NestJS RBAC standarti — docs): nafaqat "admin'mi?", balki "bu postni tahrirlay oladimi?" (ownership + shart bir joyda).
ability.can("update", post)— post egasi bo'lsa true. Murakkab loyihada (granular ruxsat) — CASL. Oddiy loyihada — RolesGuard 2.3-bob.
2.9. CASL ability (ta'rif)
import { AbilityBuilder, PureAbility } from "@casl/ability";
// Foydalanuvchiga qarab ability (kim nima qila oladi)
function defineAbility(user: User) {
const { can, cannot, build } = new AbilityBuilder(PureAbility);
if (user.rol === "admin") {
can("manage", "all"); // admin — hammasi
} else {
can("read", "Post"); // har kim post o'qiydi
can("create", "Post");
can("update", "Post", { authorId: user.id }); // FAQAT o'z postini (ownership + shart!)
can("delete", "Post", { authorId: user.id });
}
return build();
}
// Tekshirish
const ability = defineAbility(user);
ability.can("update", post); // post.authorId === user.id bo'lsa trueCASL ability —
can("update", "Post", { authorId: user.id })— "o'z postini yangilaydi" (action + subject + shart). Ownership (authorId) ability ichida (alohida tekshiruv emas).ability.can("update", post)— post egasi bo'lsa true. Bu — RBAC + ownership + ABAC birga (8.dynamic).
2.10. CASL guard (dinamik)
// CASL guard + dekorator
export const CheckPolicies = (...handlers) => SetMetadata("policies", handlers);
@Injectable()
export class PoliciesGuard implements CanActivate {
constructor(private reflector: Reflector, private caslFactory: CaslAbilityFactory) {}
canActivate(context: ExecutionContext): boolean {
const policies = this.reflector.get("policies", context.getHandler()) || [];
const { user } = context.switchToHttp().getRequest();
const ability = this.caslFactory.createForUser(user); // ability (2.9)
return policies.every((handler) => handler(ability)); // shartlar
}
}
// @CheckPolicies((ability) => ability.can("update", "Post"))CASL guard — ability'ni yaratib, policy'ni tekshiradi (
ability.can(...)). Dekorator (@CheckPolicies) bilan deklarativ. Murakkab, lekin kuchli (har endpoint uchun aniq ruxsat — shart bilan). Prisma bilan ham (query-level — manba). Katta loyihada professional yechim.
2.11. RBAC vs CASL (tanlov)
RBAC (RolesGuard — 2.3):
Sodda, tushunarli (rol ruxsat)
Ko'p loyiha uchun yetarli
Murakkab/dinamik ruxsatda cheklangan (ownership alohida)
CASL 2.9-bob:
Granular (action + subject + shart)
Ownership/attribut bir joyda
Dinamik (runtime, DB'dan)
Murakkabroq, o'rganish
Oddiy: RolesGuard; murakkab/granular: CASLTanlov: RolesGuard 2.3-bob — oddiy, ko'p loyiha (admin/user). CASL 2.9-bob — murakkab, granular (o'z postini tahrirlash, shart bilan). Boshlash uchun RolesGuard; ehtiyoj o'sganda CASL. Ikkalasi ham NestJS'da standart.
2.12. nest-access-control (kutubxona)
nest-access-control (nestjsx):
- Role + Attribute based (RBAC + ABAC)
- Grant'lar (kim, nima, qaysi resursga)
- @UseRoles dekorator
CASL muqobili; ba'zi loyihalardanest-access-control — RBAC/ABAC kutubxonasi (CASL muqobili). Grant-asosli (rol resurs amal). Tanlov: CASL (mashhurroq, isomorphic) yoki nest-access-control. Ko'p loyiha custom RolesGuard 2.3-bob yoki CASL'ni tanlaydi.
2.13. To'liq auth + RBAC oqimi
So'rov AuthGuard (8.9 — JWT req.user)
RolesGuard (rol tekshir — 2.3)
[Service: ownership — 2.6]
Handler
Custom dekoratorlar:
@Public() — auth istisno (8.6: Misol 10)
@Roles("admin") — rol 2.3-bob
@CurrentUser() — req.user (8.6: 2.9)To'liq oqim: AuthGuard (kim — 8.9) RolesGuard (rol — 2.3) service ownership 2.6-bob handler. Dekoratorlar (
@Public,@Roles,@CurrentUser) — deklarativ. Bu — NestJS auth+RBAC'ning to'liq rasmi (8.9 bilan birga).
2.13a. @CurrentUser custom dekorator (param decorator — 8.6: 2.9)
Ownership 2.6-bob va handler'larda req.user'ni toza olish uchun param dekorator yozamiz. @Req() req req.user o'rniga to'g'ridan-to'g'ri @CurrentUser() user:
// current-user.decorator.ts (8.6: 2.9 — createParamDecorator)
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
export const CurrentUser = createParamDecorator(
(data: keyof User | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user; // AuthGuard qo'ygan (8.9, 2.1)
return data ? user?.[data] : user; // @CurrentUser("id") — bitta maydon
},
);// Ishlatish — toza (req.user o'rniga)
@Get("profil")
@UseGuards(AuthGuard)
profil(@CurrentUser() user: User) { // to'liq user obyekti
return user;
}
@Post("buyurtma")
@UseGuards(AuthGuard)
yarat(@CurrentUser("id") userId: number, @Body() dto: CreateOrderDto) {
return this.ordersService.yarat(userId, dto); // faqat id kerak bo'lsa
}
@CurrentUser(8.6: 2.9 —createParamDecorator) —req.user'ni handler parametrida toza oladi.AuthGuard(8.9)req.user'ni qo'yadi 2.1-bob, bu dekorator uni chiqarib beradi.dataargument bilan bitta maydonni ham olish mumkin:@CurrentUser("id") userId: number. Bu —@Req() req+req.user'dan toza va tip-xavfsiz; ownership (2.6, Misol 4) va profil endpoint'larida doim ishlatiladi. Guard'siz (AuthGuard'siz)userundefinedbo'lishini unutmang — shuning uchun@UseGuards(AuthGuard)bilan birga.
2.14. Best practices (RBAC — 14)
AuthGuard RolesGuard tartibi (auth birinchi — 2.1)
Rol enum (oddiy) yoki Permission entity (dinamik — 2.2)
Ownership tekshir (rol yetarli emas — IDOR — 2.6, 14)
Deny by default (ruxsat aniq berilmasa — yo'q — 5.17: 2.11)
Murakkab CASL (granular, shart — 2.9)
@Public global guard istisno 8.6-bob
Frontend yashirish — UX; backend — HAQIQIY himoya (14)
Rol/ruxsatni token'da 8.9-bob yoki DB'dan (sezgir — 5.17: 2.4)3. Sintaksis — tez ma'lumotnoma
// Rol-asosli (2.3)
@Roles("admin") @UseGuards(AuthGuard, RolesGuard) @Delete(":id") ochir() {}
// Permission-asosli (2.5)
@Permissions("sharh:o'chirish") @UseGuards(AuthGuard, PermissionsGuard)
// Ownership 2.6-bob: service'da order.userId === user.id || user.rol === "admin"
// CASL (2.9, 2.10)
@CheckPolicies((ability) => ability.can("update", "Post"))
@UseGuards(AuthGuard, PoliciesGuard)4. Batafsil kod namunalari
Misol 1 — To'liq RBAC modul (rol enum — 2.2, 2.3)
// rol.enum.ts
export enum Rol {
USER = "user",
MODERATOR = "moderator",
ADMIN = "admin",
}
// roles.decorator.ts (8.6: 2.9)
import { SetMetadata } from "@nestjs/common";
export const ROLES_KEY = "roles";
export const Roles = (...roles: Rol[]) => SetMetadata(ROLES_KEY, roles);
// roles.guard.ts (8.6: 2.7)
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const kerakliRollar = this.reflector.getAllAndOverride<Rol[]>(ROLES_KEY, [
context.getHandler(), context.getClass(),
]);
if (!kerakliRollar?.length) return true; // rol talab qilinmagan
const { user } = context.switchToHttp().getRequest();
if (!user) throw new ForbiddenException("Avtorizatsiya kerak");
const hasRole = kerakliRollar.includes(user.rol);
if (!hasRole) throw new ForbiddenException("Bu amal uchun ruxsatingiz yo'q"); // 403
return true;
}
}Misol 2 — Controller (rollar bilan — 2.3, 2.13)
@Controller("products")
@UseGuards(AuthGuard, RolesGuard) // butun controller (8.6: 2.10)
export class ProductsController {
constructor(private productsService: ProductsService) {}
@Get() // hamma (rol talab qilinmagan)
hammasi() {
return this.productsService.hammasi();
}
@Post()
@Roles(Rol.ADMIN) // faqat admin (2.3)
yarat(@Body() dto: CreateProductDto) {
return this.productsService.yarat(dto);
}
@Delete(":id")
@Roles(Rol.ADMIN, Rol.MODERATOR) // admin yoki moderator
ochir(@Param("id", ParseIntPipe) id: number) {
return this.productsService.ochir(id);
}
}Misol 3 — Permission-asosli (2.5)
// permissions (rol ruxsatlar — 5.17: 2.10)
export const RUXSATLAR: Record<Rol, string[]> = {
[Rol.USER]: ["product:read", "order:create", "order:read:own"],
[Rol.MODERATOR]: ["product:read", "review:delete", "order:read"],
[Rol.ADMIN]: ["*"],
};
export const Permissions = (...p: string[]) => SetMetadata("permissions", p);
@Injectable()
export class PermissionsGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const kerakli = this.reflector.getAllAndOverride<string[]>("permissions", [
context.getHandler(), context.getClass(),
]);
if (!kerakli?.length) return true;
const { user } = context.switchToHttp().getRequest();
const ruxsatlar = RUXSATLAR[user.rol] || [];
const ruxsat = ruxsatlar.includes("*") || kerakli.every((p) => ruxsatlar.includes(p));
if (!ruxsat) throw new ForbiddenException("Ruxsat yo'q");
return true;
}
}
// Ishlatish
@Permissions("review:delete")
@UseGuards(AuthGuard, PermissionsGuard)
@Delete("reviews/:id")
ochirSharh() {}Misol 4 — Ownership (service'da — 2.6, 14)
@Injectable()
export class OrdersService {
constructor(@InjectRepository(Order) private repo: Repository<Order>) {}
async bitta(id: number, user: User): Promise<Order> {
const order = await this.repo.findOne({ where: { id }, relations: { user: true } });
if (!order) throw new NotFoundException("Buyurtma topilmadi");
// OWNERSHIP — o'ziniki yoki admin (5.17: 2.8, 14)
if (order.user.id !== user.id && user.rol !== Rol.ADMIN) {
throw new ForbiddenException("Bu buyurtma sizniki emas"); // IDOR himoyasi (14)
}
return order;
}
async ochir(id: number, user: User): Promise<void> {
await this.bitta(id, user); // ownership tekshiradi (qayta ishlatish)
await this.repo.delete(id);
}
}
// Controller — @CurrentUser bilan (8.6: 2.9)
@Get(":id")
@UseGuards(AuthGuard)
bitta(@Param("id", ParseIntPipe) id: number, @CurrentUser() user: User) {
return this.ordersService.bitta(id, user);
}Misol 5 — Rol iyerarxiyasi (2.4)
const ROL_DARAJA: Record<Rol, number> = {
[Rol.USER]: 1,
[Rol.MODERATOR]: 2,
[Rol.ADMIN]: 3,
};
export const MinRole = (rol: Rol) => SetMetadata("minRole", rol);
@Injectable()
export class MinRoleGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const minRol = this.reflector.get<Rol>("minRole", context.getHandler());
if (!minRol) return true;
const { user } = context.switchToHttp().getRequest();
if (ROL_DARAJA[user.rol] < ROL_DARAJA[minRol]) {
throw new ForbiddenException("Ruxsat yo'q");
}
return true;
}
}
// @MinRole(Rol.MODERATOR) moderator va admin o'tadi (5.17: 2.7)Misol 6 — CASL ability factory (2.9 — promptda yo'q)
import { AbilityBuilder, PureAbility, ExtractSubjectType } from "@casl/ability";
import { Injectable } from "@nestjs/common";
type Action = "manage" | "create" | "read" | "update" | "delete";
type Subjects = "Post" | "Order" | "User" | "all";
@Injectable()
export class CaslAbilityFactory {
createForUser(user: User) {
const { can, cannot, build } = new AbilityBuilder<PureAbility<[Action, Subjects]>>(PureAbility);
if (user.rol === Rol.ADMIN) {
can("manage", "all"); // admin — hammasi (2.9)
} else {
can("read", "Post"); // o'qish — hamma
can("create", "Post");
can("update", "Post", { authorId: user.id } as any); // O'Z postini (ownership + shart!)
can("delete", "Post", { authorId: user.id } as any);
cannot("delete", "Post", { published: true } as any); // e'lon qilinganni emas
}
return build({
detectSubjectType: (item) => item.constructor.name as ExtractSubjectType<Subjects>,
});
}
}Misol 7 — CASL guard + policy (2.10)
// policies decorator
type PolicyHandler = (ability: any) => boolean;
export const CheckPolicies = (...handlers: PolicyHandler[]) => SetMetadata("policies", handlers);
@Injectable()
export class PoliciesGuard implements CanActivate {
constructor(private reflector: Reflector, private caslFactory: CaslAbilityFactory) {}
canActivate(context: ExecutionContext): boolean {
const policies = this.reflector.get<PolicyHandler[]>("policies", context.getHandler()) || [];
const { user } = context.switchToHttp().getRequest();
const ability = this.caslFactory.createForUser(user); // (2.9, Misol 6)
return policies.every((handler) => handler(ability));
}
}
// Ishlatish (deklarativ — 2.10)
@UseGuards(AuthGuard, PoliciesGuard)
@CheckPolicies((ability) => ability.can("create", "Post"))
@Post()
yaratPost() {}// casl.module.ts — CaslAbilityFactory'ni DI'ga ulash (8.2)
import { Module } from "@nestjs/common";
@Module({
providers: [CaslAbilityFactory], // Misol 6
exports: [CaslAbilityFactory], // PoliciesGuard va service'lar (Misol 8) import qiladi
})
export class CaslModule {}
// Ishlatuvchi modul: @Module({ imports: [CaslModule] }) — endi factory inject qilinadi
CaslModule—CaslAbilityFactory'ni (Misol 6) provider qilib,exportqiladi.PoliciesGuard(Misol 7) vaPostsService(Misol 8) uni inject qiladi — shuning uchun ularni ishlatadigan modulimports: [CaslModule]yozadi. Global guard (Misol 10) uchun ham factory shu modul orqali DI'ga tushadi.
Misol 8 — CASL service'da (ownership shart — 2.9)
// Murakkab ownership — CASL bilan (2.9)
@Injectable()
export class PostsService {
constructor(
@InjectRepository(Post) private repo: Repository<Post>,
private caslFactory: CaslAbilityFactory,
) {}
async yangila(id: number, dto: UpdatePostDto, user: User): Promise<Post> {
const post = await this.repo.findOne({ where: { id } });
if (!post) throw new NotFoundException();
const ability = this.caslFactory.createForUser(user); // (2.9)
if (!ability.can("update", post)) { // post.authorId tekshiriladi!
throw new ForbiddenException("Bu postni tahrirlay olmaysiz"); // (14)
}
Object.assign(post, dto);
return this.repo.save(post);
}
}
// CASL — ownership + shart bir tekshiruvda (ability.can(post) — post atributlari)Misol 9 — Rol tayinlash (admin — 5.17: Misol 8)
@Controller("admin/users")
@UseGuards(AuthGuard, RolesGuard)
@Roles(Rol.ADMIN) // butun controller — admin
export class AdminUsersController {
constructor(private usersService: UsersService) {}
@Patch(":id/role")
rolTayinla(@Param("id", ParseIntPipe) id: number, @Body() dto: AssignRoleDto) {
// dto.rol — enum validatsiya (8.5: @IsEnum)
return this.usersService.rolTayinla(id, dto.rol);
}
}
// DTO
export class AssignRoleDto {
@IsEnum(Rol, { message: "Noto'g'ri rol" }) // (8.5)
rol: Rol;
}Misol 10 — Global RBAC sozlash (8.6: 2.12)
// app.module.ts — global guard'lar (DI — 8.6: 2.12)
@Module({
providers: [
{ provide: APP_GUARD, useClass: AuthGuard }, // 1. global auth (8.9)
{ provide: APP_GUARD, useClass: RolesGuard }, // 2. global roles (tartib — 2.1)
CaslAbilityFactory,
],
})
export class AppModule {}
// Endi @Public() — auth istisno; @Roles() — rol; default — auth talab qilinadi
// Public endpoint: @Public() @Post("login") (8.6: Misol 10)5. To'g'ri va noto'g'ri holatlar
1) Faqat rol, ownership'siz
"user roli bor" har buyurtmani o'chiradi (IDOR — 14, 2.6)
ownership tekshir (order.userId === user.id)2) Guard tartibi (roles auth'dan oldin)
// roles avval (req.user yo'q)
@UseGuards(RolesGuard, AuthGuard)
// auth birinchi (2.1)
@UseGuards(AuthGuard, RolesGuard)3) Frontend'ga ishonish
"admin tugmasini yashirdim" (hacker API'ga — 14, 5.17: 2.11)
backend guard (haqiqiy himoya)4) Default ruxsat berish
// noma'lum rolga ruxsat (14)
if (user.rol === "banned") return false; return true; // qolganlarga ruxsat!
// deny by default (faqat ruxsat etilganlar)
return kerakliRollar.includes(user.rol);5) Murakkab ruxsatni RolesGuard'da
"o'z postini tahrirlash" RolesGuard'da (cheklangan — 2.11)
CASL (ownership + shart — 2.9)6. Keng tarqalgan xatolar va yechimlari
Xato 1 — Cannot read 'rol' of undefined
Sababi: req.user yo'q (AuthGuard ishlamagan yoki tartib — 2.1). Yechimi: AuthGuard RolesGuard'dan oldin.
Xato 2 — Admin ham 403 oladi
Sababi: rol nomi mos emas (enum) yoki token'da rol yo'q 2.2-bob. Yechimi: enum izchil; token payload'da rol 8.9-bob.
Xato 3 — Ownership ishlamaydi
Sababi: service'da tekshirilmagan 2.6-bob. Yechimi: order.userId === user.id || admin.
Xato 4 — CASL ability subject aniqlamaydi
Sababi: detectSubjectType yo'q 2.9-bob. Yechimi: detectSubjectType (constructor.name).
Xato 5 — Global guard hamma joyni bloklaydi
Sababi: @Public istisnosi yo'q (8.6: 2.9). Yechimi: @Public + guard tekshiruvi.
Xato 6 — Rol o'zgartirildi, eski token amal qiladi
Sababi: token'da eski rol (5.17: 2.4). Yechimi: qisqa token + refresh 5.16-bob; yoki DB'dan rol.
7. Integratsiya — bu mavzu stack'ning qayerida uchraydi
- RBAC 5.17-bob: Express'dagi RBAC — NestJS'da.
- Guards 8.6-bob: RolesGuard, PoliciesGuard.
- Auth (8.9, 5.15): AuthGuard — req.user.
- Custom decorator (8.6: 2.9): @Roles, @CurrentUser.
- DTO 8.5-bob: rol validatsiya (@IsEnum).
- DB 8.3-bob: User/Role entity.
- Ownership (5.17: 2.8): IDOR himoyasi (14).
- CASL: dinamik ruxsat (promptda yo'q).
- Xavfsizlik (14): broken access control (OWASP).
8. Eng yaxshi amaliyotlar (best practices)
- AuthGuard RolesGuard tartibi (auth birinchi — 2.1).
- Rol enum (oddiy) yoki Permission entity (dinamik — 2.2, 2.5).
- Ownership tekshir (rol yetarli emas — IDOR — 2.6, 14).
- Deny by default (ruxsat aniq berilmasa — yo'q — 14).
- Murakkab CASL (granular, shart, ownership — 2.9).
- @Public (global guard istisno — 8.6).
- Frontend yashirish — UX; backend — haqiqiy himoya (14).
- @CurrentUser (toza req.user — 8.6).
- Rol validatsiya (@IsEnum — DTO — 8.5).
- Rol tayinlashni himoyala (faqat admin — Misol 9).
9. Amaliy loyiha: "To'liq RBAC Tizimi (NestJS)"
NestJS RBAC'ni mustahkamlash.
Maqsad
NestJS'da to'liq RBAC tizimini qurish: rol-asosli, permission-asosli, ownership, va CASL (granular).
Talablar (requirements)
- User-Role: rol enum (yoki entity); token'da rol (8.9 — Misol 1, 2.2).
- RolesGuard + @Roles: rol-asosli; auth tartibi (Misol 1, 2, 2.3).
- Rol iyerarxiyasi: @MinRole (Misol 5, 2.4).
- Permission-asosli: @Permissions + guard (Misol 3, 2.5).
- Ownership: service'da (o'z resursi — Misol 4, 2.6, 14).
- CASL (bonus): ability factory + PoliciesGuard (granular — Misol 6, 7, 8, 2.9).
- Rol tayinlash: faqat admin (Misol 9).
- Global RBAC: APP_GUARD + @Public (Misol 10, 8.6).
- Deny by default: ruxsat aniq berilmasa — yo'q (14).
- DTO: rol validatsiya (@IsEnum — 8.5).
Maslahatlar (hint)
- Auth roles tartibi (2.1, 2-xato).
- Ownership: service'da (rol yetarli emas — 2.6, 1-xato).
- CASL: can(action, subject, { shart }) 2.9-bob.
- @Public global istisno (8.6, 5-xato).
- Deny by default (14, 4-xato).
- @CurrentUser (8.6: 2.9).
"Tayyor" mezonlari (acceptance criteria)
- User-Role (rol token'da).
- RolesGuard + @Roles (tartib to'g'ri).
- Rol iyerarxiyasi (@MinRole).
- Permission-asosli.
- Ownership (IDOR himoyasi).
- (Bonus) CASL (granular, shart).
- Rol tayinlash (admin).
- Global RBAC + @Public.
- Deny by default.
- Rol validatsiya (@IsEnum).
Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.
10. Xulosa va keyingi bobga ko'prik
Bu bobda NestJS'da to'liq RBAC'ni o'rgandik:
- Authentication vs Authorization (8.9 vs bu bob — 2.1); User-Role modeli (enum/entity — 2.2); User modul (entity/service/module — 2.2a); @CurrentUser (param dekorator — 2.13a).
- Rol-asosli (@Roles + RolesGuard — 2.3); iyerarxiya (@MinRole — 2.4); permission-asosli (@Permissions — 2.5).
- Ownership (o'z resursi — IDOR himoyasi — 2.6, 14); ownership guard 2.7-bob.
- CASL (dinamik, attribut-asosli — ability + policy — 2.9, 2.10); RBAC vs CASL 2.11-bob; to'liq oqim 2.13-bob.
Keyingi bob — 8.8-bob: File upload, Swagger. RBAC'ni bildik; endi ikki amaliy mavzuni — NestJS'da fayl yuklash (FileInterceptor — Multer ustida — 5.11) va Swagger (API hujjat — 5.23) — o'rganamiz. Fayl yuklash va avtomatik hujjat — har real API'da kerak.
Foydalanilgan rasmiy/ishonchli manbalar
- docs.nestjs.com/security/authorization (RBAC, claims, CASL integration)
- DEV / Medium — Role-Based Access Control in NestJS; CASL vs RBAC 2026
- github.com/nestjsx/nest-access-control; @casl/ability (dinamik ruxsat)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!