Mundarija (28)
- Bu darsda
- 1. Nega bu kerak?
- 2. CASL asoslari
- 2.1 Qoidalar
- 2.2 Tur va nusxa
- 3. Nest'da: PoliciesGuard va @CheckPolicies
- 3.1 Guard va dekorator
- 3.2 Biz ishga tushirdik
- 3.3 Ro'yxatlar: accessibleBy
- 3.4 Qoidalar qayerda yashaydi
- 4. Kanon: nega CASL yo'q, egalik qanday
- 4.1 O'lchov va qaror
- 4.2 Egalik Nest'da: fabrika + CLS
- 4.3 Topilgan xato: xotiradagi ma'lumotni RLS himoya qilmaydi
- 4.4 Egalik xaritasi
- 5. Hujumchi nigohi
- 6. Ko'p uchraydigan xatolar
- 7. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): cannot va ustunlik
- 3-mashq (qiyin): egasi bilan hodisalar
- 8. Loyiha qadami: egalik — fabrika, CLS, SSE va eksport
- 8.1 Fayllar
- 8.2 OMBOR_FABRIKASI provider'i
- 8.3 Tekshiruv: olti kutilgan xato
- 9. Real ishda
- Xulosa
- Manbalar
CASL bilan policy-based avtorizatsiya NestJS'da: can, cannot, PoliciesGuard va RLS
Qisqacha: CASL — "kim nima qila oladi" qoidalarini bitta joyda yozish kutubxonasi:
can("update", "Vazifa", { egasiId: kim.id }). Nest'da uPoliciesGuardva@CheckPolicies(...)dekoratori bilan ulanadi. Asosiy tuzoq: guard odatda faqat turni tekshiradi ("Vazifa'ni yangilay oladimi?") — bu savolga hamma "ha" deydi. Aniq vazifani (nusxani) handler ichida tekshirmasangiz, Aziz Dilnozaning vazifasini o'zgartira oladi — biz buni haqiqiy so'rovda ko'rdik.vazifalar-apida CASL yo'q: ikki rol va egalik, egalikni esa Postgres RLS ta'minlaydi. Ko'chishda topilgan xato: SSE oqimi va eksport ishlari xotirada edi — RLS ularni himoya qilmagan.
Bu darsda
- CASL qoidalarini (
can,cannot, shartlar,because) yozasiz va qobiliyatni (ability) tekshirasiz. - Nest'da
PoliciesGuardva@CheckPoliciesni ulaysiz. - Tur tekshiruvi va nusxa tekshiruvi farqini haqiqiy BOLA misolida ko'rasiz.
- Kanon nega CASL o'rniga RLS tanlaganini va egalikni Nest'da qanday o'tkazganini (fabrika + CLS) tushunasiz.
- Xotiradagi ma'lumot (SSE, eksport) egalikdan chetda qolgan xatoni tuzatasiz.
Oldin bilishingiz kerak: Global auth guard, @Public va @Roles, ABAC, ReBAC va policy engine'lar, RBAC va resurs egaligi, So'rov konteksti: nestjs-cls, Server-Sent Events NestJS'da.
O'tgan darsda guard "kimsan?" va "qaysi rolda?" degan savollarga javob berdi. Lekin vazifalar-api da eng ko'p beriladigan savol boshqa: "bu vazifa senikimi?". Rol bunga javob bermaydi — Aziz ham, Dilnoza ham oddiy foydalanuvchi.
1. Nega bu kerak?
RBAC va resurs egaligi darsida egalikni ikki qatlamda qurgandik. Birinchisi — har SQL so'rovda WHERE egasi_id = $1. Ikkinchisi — Postgres RLS (qator darajasidagi xavfsizlik). ABAC, ReBAC va policy engine'lar darsida esa qoidalar murakkablashsa — "siyosat (policy) dvigateli" kerak bo'lishini aytgandik.
Nest dunyosida eng mashhur siyosat kutubxonasi — CASL. Nest hujjatining avtorizatsiya bo'limida unga alohida qism ajratilgan. Ko'p jamoalar "faqat o'z postini tahrirlash", "moderator faqat o'z bo'limida" kabi qoidalarni aynan CASL bilan yozadi.
O'xshatish: CASL — bino qo'riqchisining yon daftari. Unda yozilgan: "Aziz — faqat 12-xonaga", "farrosh — hamma xonaga, lekin seyfga emas". Qo'riqchi har safar daftarga qaraydi. Muammo bitta: agar qo'riqchi "Aziz xonaga kira oladimi?" deb so'rasa (qaysi xona ekanini aytmasdan) — daftar "ha" deydi.
2. CASL asoslari
2.1 Qoidalar
@casl/ability paketi. Qoida to'rt qismdan iborat: amal (action), mavzu (subject), shart (conditions) va ixtiyoriy sabab. Qobiliyat (ability) — bitta foydalanuvchi uchun yig'ilgan qoidalar to'plami:
// ability.factory.ts — kim nima qila oladi (CASL qoidalari)
import { Injectable } from "@nestjs/common";
import { AbilityBuilder, createMongoAbility } from "@casl/ability";
import type { MongoAbility } from "@casl/ability";
import type { Rol } from "../foydalanuvchilar.js";
export type Amal = "manage" | "read" | "update" | "delete" | "block";
export type Mavzu = "Vazifa" | "Foydalanuvchi" | "all";
export type Qobiliyat = MongoAbility<[Amal, Mavzu | object]>;
export type Kim = { id: number; rol: Rol };
@Injectable()
export class AbilityFactory {
yarat(kim: Kim): Qobiliyat {
const { can, cannot, build } =
new AbilityBuilder<Qobiliyat>(createMongoAbility);
// har kim — faqat O'Z vazifasini (shart: egasiId)
can(["read", "update", "delete"], "Vazifa", { egasiId: kim.id });
if (kim.rol === "admin") {
can(["read", "block"], "Foydalanuvchi");
cannot("block", "Foydalanuvchi", { id: kim.id })
.because("O'zingizni bloklay olmaysiz");
}
return build();
}
}Qatorma-qator:
createMongoAbility— shartlar MongoDB so'rovi uslubida yoziladi ({ egasiId: 1 },{ narx: { $lt: 100 } }). Bu faqat sintaksis: baza Mongo bo'lishi shart emas.can([...], "Vazifa", { egasiId: kim.id })— "Vazifa'ni o'qish, yangilash, o'chirish mumkin, agar uningegasiIdsi mening id'im bo'lsa".cannot(...)— taqiq. Keyin yozilgan qoida ustun: admin hamma foydalanuvchini bloklay oladi, faqat o'zini emas..because(...)— taqiq sababi, xato xabarida ko'rinadi.
Bu qoidalar 28-qismdagi siyosatning aynan o'zi: foydalanuvchi — o'z vazifalari; admin — hisoblarni boshqaradi, lekin o'zini bloklay olmaydi va begona vazifalarni ko'rmaydi.
Versiya: kanon @casl/ability 7.0.2 bilan tajriba qildi. 7.1.0 2026-10-28 da chiqqan edi — kursning "7 kundan eski" qoidasidan o'tmadi. Biz ham demoda 7.0.2 ni o'rnatdik.
2.2 Tur va nusxa
CASL ikki xil savolni qabul qiladi:
q.can("update", "Vazifa")— tur bo'yicha: "umuman biror vazifani yangilay oladimi?"q.can("update", subject("Vazifa", v))— nusxa bo'yicha: "aynan shu vazifani?"
Bu farq nega muhim? Chunki ikkinchi savolni unutish — API xavfsizligidagi eng ko'p uchraydigan xato, BOLA (Broken Object Level Authorization — obyekt darajasidagi ruxsat buzilishi). OWASP API ro'yxatida u birinchi o'rinda; biz uni RBAC va resurs egaligi darsida ko'rgandik: /vazifalar/20 ni so'ragan har kim 20-vazifani oladi, egasi kim bo'lishidan qat'i nazar.
Shartli qoida tur savoliga doim "ha" deydi: shart tekshirib bo'lmaydi, chunki aniq obyekt yo'q. Paketsiz modelda ko'ramiz (brauzerda ishlaydi):
function qobiliyat(kim) {
const qoidalar = [
{ amal: "update", mavzu: "Vazifa", shart: { egasiId: kim.id } },
];
return {
can(amal, mavzu, obyekt) {
return qoidalar.some((q) => {
if (q.amal !== amal || q.mavzu !== mavzu) return false;
// tur savoli: shart e'tiborsiz
if (obyekt === undefined) return true;
return Object.entries(q.shart)
.every(([k, v]) => obyekt[k] === v);
});
},
};
}
const aziz = qobiliyat({ id: 1 });
const dilnozaniki = { id: 20, egasiId: 2 };
console.log("tur:", aziz.can("update", "Vazifa"));
console.log("nusxa:", aziz.can("update", "Vazifa", dilnozaniki));
const oziniki = { id: 10, egasiId: 1 };
console.log("o'ziniki:", aziz.can("update", "Vazifa", oziniki));Konsolda:
tur: true
nusxa: false
o'ziniki: trueBirinchi qatorga qarang. CASL hujjati ham buni ochiq aytadi: tur bo'yicha tekshiruvda shartlar e'tiborga olinmaydi va natija "kamida bitta mos obyekt bo'lishi mumkin" degani. Modelda soddalashtirdik: haqiqiy CASL $in, $lt kabi operatorlarni, maydon cheklovlarini va qoidalar ustunligini ham biladi.
Tekshirib ko'ring: Nega CASL tur savoliga "yo'q" demaydi? Axir Aziz ko'p vazifani yangilay olmaydi.
Javob
Tur savoli — "bu amal umuman ruxsat etilganmi?" degani. Aziz ba'zi vazifalarni (o'zinikini) yangilay oladi, shuning uchun javob "ha". Bu savol menyuda tugmani ko'rsatish yoki yashirish uchun foydali. Lekin aniq vazifani o'zgartirishdan oldin nusxa savoli shart. "Ha" ni "hamma vazifani" deb tushunish — BOLA xatosi.
3. Nest'da: PoliciesGuard va @CheckPolicies
3.1 Guard va dekorator
Nest hujjatidagi naqsh — siyosat funksiyasini metama'lumot sifatida yozish va guard'da bajarish:
export type Siyosat = (q: Qobiliyat) => boolean;
const KALIT = "check_policy";
export const CheckPolicies = (...s: Siyosat[]) =>
SetMetadata(KALIT, s);
@Injectable()
export class PoliciesGuard implements CanActivate {
constructor(private readonly reflector: Reflector,
private readonly factory: AbilityFactory) {}
canActivate(ctx: ExecutionContext): boolean {
const s = this.reflector.get<Siyosat[] | undefined>(
KALIT, ctx.getHandler()) ?? [];
const req = ctx.switchToHttp().getRequest<Request>();
const q = this.factory.yarat(req.user as Kim);
req.qobiliyat = q; // handler ham ishlatsin
return s.every((f) => f(q));
}
}Guard foydalanuvchi uchun qobiliyat quradi, uni req ga qo'yadi va dekoratordagi siyosatlarni tekshiradi. Guard ishlaganda resurs hali bazadan o'qilmagan — u faqat :id ni biladi. Shuning uchun guard'da faqat tur tekshiruvi qilish mumkin.
3.2 Biz ishga tushirdik
Ikki marshrut: biri faqat guard bilan (tur tekshiruvi), ikkinchisi handler ichida nusxani ham tekshiradi:
// NOTO'G'RI: faqat TUR tekshiruvi: "Vazifa"ni yangilay oladimi
@Patch("tur/:id")
@CheckPolicies((q) => q.can("update", "Vazifa"))
turBilan(@Param("id", ParseIntPipe) id: number,
@Body() b: { matn: string }): Vazifa {
const v = ol(id);
v.matn = b.matn;
return v;
}
// TO'G'RI: NUSXA tekshiruvi: aynan shu vazifani (egasiId bilan)
@Patch(":id")
@CheckPolicies((q) => q.can("update", "Vazifa"))
nusxaBilan(@Param("id", ParseIntPipe) id: number,
@Body() b: { matn: string }, @Req() req: Request): Vazifa {
const v = ol(id);
try {
ForbiddenError.from(req.qobiliyat!)
.throwUnlessCan("update", subject("Vazifa", { ...v }));
} catch (x) {
if (x instanceof ForbiddenError) {
throw new ForbiddenException(x.message);
}
throw x;
}
v.matn = b.matn;
return v;
}Vazifa 10 — Aziznik, vazifa 20 — Dilnozaniki. Aziz uchala so'rovni yubordi (NestJS 12.2.0, @casl/ability 7.0.2). Avval uchinchi qatorga qarang: yo'l /tur/20, status 200, matn "Buzildi":
Aziz PATCH /vazifalar/10 → 200 {"id":10,"matn":"Non va qatiq olish","egasiId":1}
Aziz PATCH /vazifalar/20 → 403 {"message":"Cannot execute \"update\" on \"Vazifa\"","error":"Forbidden","statusCode":403}
Aziz PATCH /vazifalar/tur/20 → 200 {"id":20,"matn":"Buzildi","egasiId":2}
can('update', 'Vazifa') — turi: true
can('update', Dilnozaniki): false
admin o'zini bloklay oladimi: false — O'zingizni bloklay olmaysizUchinchi qator — haqiqiy BOLA. Guard va dekorator bor, kod "himoyalangan" ko'rinadi. Lekin Dilnozaning vazifasi "Buzildi" ga aylandi. To'rtinchi va beshinchi qatorlar sababni ko'rsatadi: tur savoliga true, nusxa savoliga false.
ForbiddenError.from(q).throwUnlessCan(...) — CASL'ning yordamchisi: ruxsat bo'lmasa xato tashlaydi. Biz uni Nest'ning ForbiddenException iga aylantirdik — aks holda Nest uni noma'lum xato deb 500 qaytarardi.
3.3 Ro'yxatlar: accessibleBy
Bitta vazifani tekshirish — oson. Ro'yxat-chi? GET /vazifalar ming qatorni o'qib, keyin CASL bilan filtrlash — sekin va xavfli (sahifalash buziladi). Buning uchun CASL qoidalarni baza so'roviga aylantiradi: @casl/prisma paketidagi accessibleBy(q).Vazifa Prisma'ning where shartini qaytaradi, @casl/mongoose — Mongo filtrini. Biz bu paketlarni sinamadik; hujjatga ko'ra qoidadagi { egasiId: kim.id } sharti where: { egasiId: 1 } ga aylanadi.
Ya'ni oxir-oqibat egalik baribir bazada tekshiriladi. Bu kanon tanlovining kaliti.
3.4 Qoidalar qayerda yashaydi
Demoda qoidalar kodda — AbilityFactory.yarat() ichida. Kichik loyiha uchun bu eng yaxshi joy: qoida o'zgarsa — commit, review, test. Kattaroq tizimlarda qoidalar bazaga ko'chadi: administrator panelda "moderator — izohlarni o'chira oladi" deb belgilaydi, server esa qoidalarni JSON sifatida o'qib, CASL'ga beradi. CASL qoidalari oddiy obyektlar massivi bo'lgani uchun buni qila oladi.
Yana bir qulaylik — frontend. O'sha JSON qoidalar brauzerga yuborilsa, @casl/react "Tahrirlash" tugmasini faqat ruxsati borlarga ko'rsatadi. Lekin bu faqat qulaylik: tugmani yashirish himoya emas. Hujumchi so'rovni qo'lda yuboradi, shuning uchun server baribir tekshiradi. Autentifikatsiya II darsida Next.js'da rolga asoslangan UI'ni xuddi shu qoida bilan ko'ramiz.
Qoidalar soni o'sganda yana bir xavf paydo bo'ladi: ular bir-biriga zid bo'lib qoladi. Shuning uchun CASL loyihalarida qoidalar uchun alohida testlar yoziladi: "moderator — bo'limdagi postni o'chira oladi, boshqa bo'limdagini — yo'q". Bu testlar HTTP'siz, millisoniyalarda ishlaydi.
4. Kanon: nega CASL yo'q, egalik qanday
4.1 O'lchov va qaror
Kanon tajribasi 28-qism qoidalarini CASL'da yozdi va narxini o'lchadi (kanon chiqishi):
### 3. CASL 7.0.2: 28 qoidalari (foydalanuvchi — o'z vazifalari; admin — foydalanuvchilar ro'yxati va bloklash, o'zini emas)
aziz: o'z vazifasini update — true; begonasini — false; foydalanuvchilar ro'yxati — false
admin: boshqani block — true; o'zini — false (sabab: o'zini bloklay olmaydi); begona vazifani o'qish — false
narx: qobiliyat qurish 0.85 µs/ta (100 000), can() 0.360 µs/ta (1 000 000)Tezlik muammo emas: mikrosoniyaning bir qismi. Taqqoslash uchun: 28-qismda sessiya tekshiruvi ~1.6 ms edi — bitta can() dan to'rt mingdan ortiq baravar uzoq. Ya'ni CASL qo'shilsa ham, so'rov vaqtida uni sezib bo'lmaydi. Qoidalar ham to'liq ifodalandi: ikkala qator 28-qismdagi siyosatni aynan takrorladi. Unda nega yo'q?
| Savol | vazifalar-api da |
|---|---|
| Nechta rol? | ikki: foydalanuvchi, admin |
| Qoidalar murakkabmi? | yo'q: "o'ziniki" va "admin — hisoblar" |
| Egalik qayerda? | Postgres RLS + WHERE egasi_id |
| CASL nima qo'shadi? | uchinchi qatlam, o'sha qoidaning nusxasi |
Kanon qarori (docs/AUTH-QAROR.md): CASL qo'shilmaydi. Foydasi — atributli qoidalar ko'payganda: "moderator — faqat o'z bo'limida, ish vaqtida", "jamoa a'zosi — loyiha arxivlanmagan bo'lsa". Bunday talab paydo bo'lsa — qayta ko'riladi.
Qachon qayta ko'rish kerakligini bilish uchun uch signalga qarang. Birinchisi — if (rol === …) tekshiruvlari bir nechta controller va xizmatga tarqab ketgan. Ikkinchisi — qoidalarda vaqt, holat yoki munosabat paydo bo'lgan: "faqat ish kunlari", "faqat qoralama holatida", "faqat o'z jamoasining loyihasi". Uchinchisi — frontend ham aynan shu qoidalarni bilishi kerak. Bitta signal bo'lsa — kutish mumkin. Uchalasi birdan bo'lsa — siyosat bitta joyga yig'ilishi kerak, va CASL buning eng arzon yo'li.
Bunday holatda ham RLS qoladi. CASL — ilova qatlamidagi qaror, RLS — oxirgi to'siq. Ular bir-birini almashtirmaydi, to'ldiradi.
4.2 Egalik Nest'da: fabrika + CLS
RLS 28-qismda qurilgan: har tranzaksiya boshida set_config('ilova.foydalanuvchi_id', …, true), Postgres esa boshqa egasining qatorlarini ko'rsatmaydi. Nest'da savol bitta: ombor so'rov egasini qayerdan biladi?
Birinchi xayol — Scope.REQUEST: har so'rovga yangi ombor. Lekin Provider scope'lari darsidagi kanon o'lchovida REQUEST scope ~43 % sekin edi (mediana 11 244 → 6 382 req/s): u butun zanjirni — xizmatni ham, controller'ni ham — har so'rovda qayta yaratadi.
Kanon yechimi — fabrika + CLS. Ombor provider'i endi ombor emas, funksiya: (egasiId) => new PgOmbori(pool, egasiId). Xizmat esa egasini nestjs-cls kontekstidan oladi (uni egasiniAniqla() middleware'i qo'ygan):
/** joriy so'rov egasi (sessiya / API kaliti / dev rejim EGASI_ID).
* Kontekstsiz chaqiruv — dasturchi xatosi */
get egasi(): number {
const e = this.cls.get<number | undefined>(CLS_EGASI);
if (e === undefined)
throw new Error("so'rov egasi aniqlanmagan (CLS)");
return e;
}
private get ombor(): VazifaOmbori {
return this.fabrika(this.egasi);
}PgOmbori — arzon obyekt: ulanish pool'da, obyekt faqat egasi raqamini saqlaydi. Xizmat va controller esa singleton qoladi.
flowchart TB
A["kimlik() — sessiya"] --> B["egasiniAniqla() — CLS"]
B --> C["KirishGuard — 401/403"]
C --> D["VazifalarService.egasi"]
D --> E["fabrika(egasi) — PgOmbori"]
E --> F["SQL: WHERE egasi_id"]
E --> G["RLS: set_config"]Ikki pastki quti — ikki qatlam. SQL'dagi WHERE unutilsa ham, RLS begona qatorni bermaydi. RLS sozlamasi unutilsa ham (set_config yo'q) — vazifalar_api roli bilan 0 qator qaytadi, hammasi emas ("yopiq yiqilish").
4.3 Topilgan xato: xotiradagi ma'lumotni RLS himoya qilmaydi
RLS — baza ichidagi himoya. Lekin vazifalar-api da ikki narsa bazadan tashqarida yashaydi:
- SSE oqimi (
GET /v1/vazifalar/oqim) — xizmatvazifa.yaratildikabi hodisalarni xotirada chiqaradi, oqim ularni hamma tinglovchiga beradi. - Eksport ishlari —
Mapda: ish id'si, holati, tayyor fayl.
28-qismda SSE yo'q edi, eksport esa bitta foydalanuvchili rejimdan qolgan edi. Nest'ga ko'chishda ikkalasi ham egasini tekshirmasdi. Biz tuzatishni qaytarib, kanon testlarini yurgizdik (Vitest 5.0.5, PostgreSQL 17.6):
FAIL test/nest-auth.test.ts > eksport: begona ish id'si — 404 (holat ham, fayl ham); egasiniki — 200
Error: expected 404 "Not Found", got 200 "OK"
FAIL test/nest-auth.test.ts > SSE /v1/vazifalar/oqim: faqat o'z hodisalari — Dilnozaning vazifasi Azizning oqimiga tushmaydi
AssertionError: expected '\nevent: vazifa.yaratildi\nid: 1\ndat…' not to contain 'Dilnozaniki'
+ data: {"id":1,"matn":"Dilnozaniki","bajarildi":false,"teglar":[],…}Birinchisi: Dilnoza Azizning eksport ishining holatini oldi — 200. Ish id'si UUID bo'lsa ham, u havolada, logda, brauzer tarixida qoladi. Ikkinchisi: Azizning ochiq oqimiga Dilnozaning yangi vazifasi to'liq matni bilan keldi. Bu — BOLA, faqat bazada emas, xotirada. (Chiqishni qisqartirdik: vaqt maydonlari va iz qatorlari olib tashlandi.)
Tuzatish — egani ma'lumot bilan birga olib yurish:
// vazifalar.service.ts — hodisa egasi bilan chiqadi
- this.hodisalar.emit(HODISA.yaratildi, v);
+ this.hodisalar.emit(HODISA.yaratildi, v, this.egasi);
// vazifalar.controller.ts — oqim faqat o'z egasinikini o'tkazadi
+ const egasi = this.xizmat.egasi;
…
- const tinglovchi = function (this: …, data: unknown): void {
+ const tinglovchi = function (this: …, data: unknown,
+ kimniki: unknown): void {
+ if (kimniki !== egasi) return;
// eksport.service.ts — ishda egasi; begona — 404 (403 emas)
- if (ish === undefined || ish.tugash <= this.soat())
+ if (ish === undefined || ish.tugash <= this.soat()
+ || ish.egasi !== this.#egasi())Begona ish uchun 404, 403 emas: 403 "bunday ish bor, lekin sizniki emas" degani bo'lardi. 28-qismdagi qoida: borligini ham oshkor qilmaymiz.
Fon ishi ham o'zgardi: eksport so'rov tugagandan keyin ham ishlaydi, CLS konteksti esa so'rov bilan tugaydi. Shuning uchun fon ishi omborni this.fabrika(ish.egasi) bilan oladi — egasi ishning o'zida saqlangan.
Tekshirib ko'ring: Nega bu xatoni 28-qismdagi 15 ta RLS testi ushlamadi?
Javob
RLS testlari bazani tekshiradi: "Dilnoza SELECT qilsa, Azizning qatorlari ko'rinmaydimi?". SSE va eksport ma'lumoti bazadan o'qilgandan keyin xotirada aylanadi. Baza o'z ishini to'g'ri qildi — har foydalanuvchiga faqat o'z qatorini berdi. Xato undan keyingi qatlamda edi. Xulosa: egalik har qatlamda kerak — baza, xotira, kesh, fon ishlari, oqimlar.
4.4 Egalik xaritasi
Darsdagi hamma tekshiruvni bitta jadvalga yig'amiz. Har qator — so'rov yo'lidagi bitta joy va u yerda "bu seniki mi?" degan savolga kim javob beradi:
| Qatlam | Nima biladi | Egalikni kim tekshiradi | Unutilsa |
|---|---|---|---|
| guard | faqat kim va :id |
hech kim — faqat tur (CASL) yoki rol | hamma uchun true |
| handler / xizmat | aniq vazifa | subject(...) (CASL) yoki xizmat |
BOLA — demodagi "Buzildi" |
| SQL | qator | WHERE egasi_id = $1 |
RLS ushlaydi |
| Postgres RLS | qator va set_config |
baza siyosati | 0 qator (yopiq yiqilish) |
| xotira: oqim, eksport, kesh | hodisa, ish, kesh kaliti | o'zingiz — egasini birga saqlab | begona ma'lumot oqadi |
Oxirgi qatorda "o'zingiz" so'ziga e'tibor bering. Baza qatlamlarida himoya ikki marta takrorlangan: biri unutilsa, ikkinchisi ushlaydi. Xotirada esa ikkinchi qatlam yo'q. Shuning uchun yangi oqim, kesh yoki fon ishi qo'shganda birinchi savol: "bu ma'lumotda egasi bormi?".
5. Hujumchi nigohi
- Tur tekshiruvi bilan to'xtash.
@CheckPolicies((q) => q.can("update", "Vazifa"))— chiroyli, lekin hamma uchuntrue. Biz ko'rdik: Dilnozaning vazifasi o'zgardi. - Ro'yxatni xotirada filtrlash. 10 000 qatorni o'qib, CASL bilan saralash — sekin va sahifalash buziladi. Shart bazaga tushishi kerak (
accessibleBy, RLS). - Xotira, kesh, oqim. Egalik faqat bazada bo'lsa — SSE, WebSocket, Redis keshi, fon ishlari chetda qoladi. Kanonda kesh kalitlari ham egasi bilan:
vz:<egasi>:…. - 403 bilan oshkor qilish. Begona resursga 403 — "u bor". 404 — "bilmayman".
- Admin hamma narsani ko'radi. 28-qism qarori: admin hisoblarni boshqaradi, lekin begona vazifalarni ko'rmaydi (maxfiylik). CASL'da ham, RLS'da ham shunday yozilgan.
6. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Hamma vazifani tahrirlash mumkin | faqat tur tekshiruvi | handler'da subject(...) bilan nusxa |
ForbiddenError — 500 |
CASL xatosi Nest xatosi emas | ForbiddenException ga aylantiring |
can() doim false |
subject() siz oddiy obyekt |
subject("Vazifa", obyekt) |
| Admin o'zini bloklaydi | cannot can dan oldin yozilgan |
taqiq — oxirida (keyingi qoida ustun) |
Sekin GET /vazifalar |
hamma qator o'qilib xotirada filtrlanadi | accessibleBy yoki RLS |
| SSE'da begona hodisalar | hodisada egasi yo'q | emit(nom, qiymat, egasi) va filtr |
7. Mashqlar
Mashqlar kurs/mashqlar/30/58-casl/ da.
1-mashq (oson): bo'shliqlarni to'ldiring
- Qoidalarni yig'adigan CASL klassi —
new (createMongoAbility) - Taqiq qoidasi —
("block", "Foydalanuvchi", { id: kim.id }) - Aniq obyektni CASL'ga tanitish —
("Vazifa", v) - Shartli qoida bo'lsa, tur bo'yicha
can("update", "Vazifa")natijasi — - Begona eksport ishiga kanon qaytaradigan status —
Yechim
- AbilityBuilder —
can,cannot,buildni beradi. - cannot — keyinroq yozilgan qoida ustun, shuning uchun taqiq
candan keyin. - subject — obyektga tur yorlig'ini yopishtiradi; usiz CASL obyekt turini bilmaydi.
- true — tur savolida shart e'tiborsiz: "kamida bittasi mumkin".
- 404 — borligini ham oshkor qilmaymiz.
2-mashq (o'rta): cannot va ustunlik
Darsdagi modelni kengaytiring: qoidaga ruxsat: true/false maydoni qo'shilsin. can(amal, mavzu, obyekt) mos keladigan qoidalarning oxirgisi ga qarasin (keyin yozilgan ustun). Admin uchun: block Foydalanuvchi — ruxsat; block Foydalanuvchi {id: 3} — taqiq. Admin (id 3) 2 ni va o'zini bloklay oladimi? Ishora: filter va at(-1).
Yechim
function qobiliyat(qoidalar) {
const mos = (q, amal, mavzu, obyekt) =>
q.amal === amal && q.mavzu === mavzu &&
Object.entries(q.shart ?? {}).every(([k, v]) => obyekt[k] === v);
return {
can(amal, mavzu, obyekt) {
const oxirgi = qoidalar
.filter((q) => mos(q, amal, mavzu, obyekt)).at(-1);
return oxirgi?.ruxsat ?? false;
},
};
}
const admin = qobiliyat([
{ amal: "block", mavzu: "Foydalanuvchi", ruxsat: true },
{ amal: "block", mavzu: "Foydalanuvchi", shart: { id: 3 },
ruxsat: false },
]);
console.log("2 ni:", admin.can("block", "Foydalanuvchi", { id: 2 }));
const ozi = { id: 3 };
console.log("o'zini:", admin.can("block", "Foydalanuvchi", ozi));
console.log("o'qish:", admin.can("read", "Foydalanuvchi", { id: 2 }));Konsolda:
2 ni: true
o'zini: false
o'qish: falseUchinchi qator: read uchun qoida yo'q — false. CASL ham shunday: ruxsat berilmagan hamma narsa taqiqlangan (yopiq sukut). Ikkinchi qator taqiq qoidasi keyin yozilgani uchun ishladi; tartibni almashtirsangiz — true chiqadi.
3-mashq (qiyin): egasi bilan hodisalar
Xotiradagi hodisa shinasini yozing: chiqar(nom, qiymat, egasi) va obuna(egasi, fn). Obunachi faqat o'z egasining hodisalarini olsin. Aziz (1) va Dilnoza (2) obuna bo'ladi, keyin uchta hodisa chiqadi. Kim nimani oldi? Ishora: Map, massiv va filtr — darsdagi SSE tuzatishining modeli.
Yechim
const obunachilar = [];
function obuna(egasi, fn) {
obunachilar.push({ egasi, fn });
}
function chiqar(nom, qiymat, egasi) {
for (const o of obunachilar) {
if (o.egasi === egasi) o.fn(nom, qiymat);
}
}
const olingan = new Map([[1, []], [2, []]]);
obuna(1, (nom, v) => olingan.get(1).push(`${nom}:${v.matn}`));
obuna(2, (nom, v) => olingan.get(2).push(`${nom}:${v.matn}`));
chiqar("vazifa.yaratildi", { matn: "Non olish" }, 1);
chiqar("vazifa.yaratildi", { matn: "Dorixona" }, 2);
chiqar("vazifa.bajarildi", { matn: "Non olish" }, 1);
console.log("Aziz:", olingan.get(1));
console.log("Dilnoza:", olingan.get(2));Konsolda:
Aziz: [ 'vazifa.yaratildi:Non olish', 'vazifa.bajarildi:Non olish' ]
Dilnoza: [ 'vazifa.yaratildi:Dorixona' ]Kanonda filtr tinglovchi ichida (if (kimniki !== egasi) return), modelda — chiqaruvchi ichida. Natija bir xil. Muhimi: egasi hodisa bilan birga yuradi. Hodisada egasi bo'lmasa, uni keyin topib bo'lmaydi.
8. Loyiha qadami: egalik — fabrika, CLS, SSE va eksport
8.1 Fayllar
| Fayl | O'zgarish |
|---|---|
src/vazifalar/ombor.ts |
OmborFabrikasi turi: (egasiId) => VazifaOmbori |
src/ombor.module.ts, src/ombor-yarat.ts |
OMBOR_FABRIKASI provider'i; postgres/prisma — fabrika; SAQLASH eksport (auth pool'i shundan) |
src/vazifalar/vazifalar.service.ts |
egasi (CLS'dan), ombor — fabrikadan, emit(…, this.egasi) |
src/eksport/eksport.service.ts |
ishda egasi; begona — 404; fon ishi — fabrika(ish.egasi) |
src/vazifalar/vazifalar.controller.ts |
SSE filtri; kursor HMAC kaliti (KURSOR_KALITI) |
src/vazifalar/pg-ombor.ts, prisma-ombor.ts |
har tranzaksiyada set_config('ilova.foydalanuvchi_id', …) (28-qism RLS) |
src/vazifalar/sorov.ts, src/ishga-tushir.ts |
kursor kaliti parametri; omborOl eksporti |
pg-ombor.ts va prisma-ombor.ts dagi o'zgarish — 28-qismda Express versiyasida qilingan RLS tuzatishlarining aynan o'zi: o'qish ham tranzaksiyada, boshida set_config. Ularni 28-qism commit'ingizdan oling.
8.2 OMBOR_FABRIKASI provider'i
ombor.module.ts dagi asosiy qism (kanon faylidan):
const fabrikaga =
(o: VazifaOmbori | OmborFabrikasi): OmborFabrikasi =>
(typeof o === "function" ? o : () => o);
const ikkalasi = [
{ provide: OMBOR_FABRIKASI,
useFactory: (s: Saqlash): OmborFabrikasi => fabrikaga(s.ombor),
inject: [SAQLASH] },
{ provide: VAZIFA_OMBORI,
useFactory: (s: Saqlash, c: Config): VazifaOmbori =>
omborOl(s, c.EGASI_ID),
inject: [SAQLASH, CONFIG] },
];Xotira va fayl ombori (DEV rejim) — bitta ombor, fabrika uni har kimga qaytaradi. Postgres va Prisma — haqiqiy fabrika. Eski VAZIFA_OMBORI tokeni qoladi — health tekshiruvi va skriptlar uchun (EGASI_ID ombori).
8.3 Tekshiruv: olti kutilgan xato
Shu kesimni biz kanon fayllaridan yig'ib tekshirdik (npx tsc --noEmit):
src/auth/qoriqchi.ts(14,32): error TS2307: Cannot find module './cheklov.guard.ts' or its corresponding type declarations.
src/auth/qoriqchi.ts(67,90): error TS7006: Parameter 'req' implicitly has an 'any' type.
src/ilova.ts(59,84): error TS2322: Type 'VazifaOmbori | OmborFabrikasi | undefined' is not assignable to type 'VazifaOmbori | undefined'.
test/pg-ombor.test.ts(199,36): error TS2339: Property 'yarat' does not exist on type 'VazifaOmbori | OmborFabrikasi'.
test/pg-ombor.test.ts(217,37): error TS2339: Property 'yarat' does not exist on type 'VazifaOmbori | OmborFabrikasi'.
test/prisma-ombor.test.ts(125,33): error TS2339: Property 'yarat' does not exist on type 'VazifaOmbori | OmborFabrikasi'.Tarjimasi va o'qish usuli:
- Birinchi ikkitasi — o'tgan darsdan (
cheklov.guard.tshali yo'q). TS2305(OmborFabrikasiyo'q) yo'qoldi — tur endi bor. Uning o'rnidaTS2322: "VazifaOmbori | OmborFabrikasi | undefinedturiniVazifaOmbori | undefinedga berib bo'lmaydi".ilova.tsfabrika beradi,AppModulehali eski turni kutadi.TS2339— "VazifaOmbori | OmborFabrikasituridayaratxususiyati yo'q". Eski testlar omborni to'g'ridan ishlatadi; fabrika bo'lishi mumkinligini bilmaydi. Testlarning yangi nusxasi Autentifikatsiya II darsida keladi.
TS2305 ning yo'qolishi — to'g'ri yo'lda ekaningiz belgisi. Faqat shu oltita bo'lsa — to'g'ri. Hali commit qilmang.
9. Real ishda
- CASL — Nest loyihalarida ABAC uchun eng ko'p uchraydigan tanlov: CMS, SaaS (tashkilot — jamoa — loyiha), marketpleys (sotuvchi — faqat o'z mahsuloti). Frontend'da ham ishlaydi (
@casl/react) — bitta qoidalar to'plami tugmalarni yashiradi va API'ni himoyalaydi. - Code review'dagi klassik topilma — guard'da tur tekshiruvi bor, handler'da nusxa yo'q.
- RLS — Postgres'da ko'p ijarachili (multi-tenant) SaaS'larning standart himoyasi; Supabase uni asosiy model qilgan.
- Intervyu: "RBAC va ABAC farqi?", "CASL'da
can("update", "Post")nima qaytaradi, agar qoida shartli bo'lsa?", "Egalikni qaysi qatlamda tekshirasiz?".
Xulosa
- CASL — qoidalar to'plami:
can/cannot, shartlar,because; Nest'daPoliciesGuard+@CheckPolicies. - Guard'da faqat tur tekshiruvi mumkin — u shartli qoidaga doim
true; aniq resurs handler'dasubject()bilan tekshiriladi. Biz ko'rdik: tur tekshiruvi bilan Dilnozaning vazifasi o'zgardi. - Ro'yxatlar uchun shart bazaga tushadi (
accessibleBy, RLS). - Kanon: CASL yo'q (2 rol, egalik — RLS); Nest'da egalik —
OMBOR_FABRIKASI+ CLS, REQUEST scope emas (−43 %). - Topilgan xato: SSE va eksport xotirada — RLS himoya qilmaydi; egasi hodisa va ish bilan birga yuradi, begona — 404.
Keyingi dars: OAuth va tayyor auth yechimlari NestJS'da — Google va Telegram kirishini Nest'da yozamiz, Better Auth'ni ko'rib chiqamiz va 28-qismdan qolgan ikki xatoni tuzatamiz.
Manbalar
- NestJS hujjati — Authorization, CASL integration: https://docs.nestjs.com/security/authorization#integrating-casl
- CASL hujjati — Define rules, Checking abilities (subject type vs instance): https://casl.js.org/v6/en/guide/intro
- CASL — Prisma integration (
accessibleBy): https://casl.js.org/v6/en/package/casl-prisma - PostgreSQL 18 — Row Security Policies: https://www.postgresql.org/docs/18/ddl-rowsecurity.html
- OWASP API Security Top 10 (2023) — API1 Broken Object Level Authorization: https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/
- Kanon (IlmHamroh, 2026-10-31 — 2026-11-01): CASL 7.0.2 tajribasi, REQUEST scope o'lchovi,
OMBOR_FABRIKASI+ CLS,test/nest-auth.test.ts. Demo (PoliciesGuard, tur va nusxa), tuzatish qaytarilgan nusxadagi test chiqishlari va oraliqtsckesimi — biz ishga tushirdik (2026-11-02, NestJS 12.2.0,@casl/ability7.0.2, PostgreSQL 17.6).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!