RequestContext
Tong quan
Tien ich useRequestContext cung cap quyen truy cap an toan kieu toi ngu canh yeu cau HTTP hien tai, bao gom nguoi dung da xac thuc, thong tin vai tro, helper uy quyen, va cac ham dinh dang phan hoi. No bao boc useRequestContextInfra() cua IGNIS Framework voi cac kieu dac thu ung dung va quy uoc BANA.
Nguon: packages/core/src/utilities/request.utility.ts (89 dong)
Import
import { useRequestContext } from '@nx/core';
// or
import { useRequestContext } from '@nx/core/utilities';Kieu Tra ve
Ham useRequestContext() tra ve doi tuong voi cac thuoc tinh sau:
| Thuoc tinh | Kieu | Mo ta |
|---|---|---|
context | Context | Doi tuong ngu canh yeu cau Hono nguyen goc |
currentUser | IJWTTokenPayload | Payload JWT da giai ma day du |
userId | string | Loi tat cho currentUser.userId |
roles | string[] | Mang cac chuoi dinh danh vai tro trich xuat tu currentUser.roles |
isAlwaysAllowed | boolean | true neu nguoi dung co vai tro SUPER_ADMIN hoac ADMIN |
useCountData | boolean | Co bao boc phan hoi mang trong dinh dang { data, count } hay khong |
normalizeCountableData | <T>(opts) => T[] | { data: T[], count: number } | Chuan hoa ket qua danh sach voi header Content-Range |
formatResponse | <T>(result, statusCode?) => Response | Dinh dang phan hoi don le |
formatArrayResponse | <T>(result, statusCode?) => Response | Dinh dang phan hoi mang |
Cach Su dung Co ban
import { useRequestContext } from '@nx/core';
@service()
export class ProductService {
async createProduct(data: CreateProductDto): Promise<TProduct> {
const { userId, roles, isAlwaysAllowed } = useRequestContext();
// Check authorization
if (!isAlwaysAllowed && !roles.includes('merchant-admin')) {
throw new ForbiddenError('Insufficient permissions');
}
// Use userId for audit trail
return this.productRepository.create({
data: {
...data,
createdBy: userId,
},
});
}
}Cac Thuoc tinh Ngu canh
currentUser
Payload token JWT da giai ma day du:
interface IJWTTokenPayload {
userId: string;
email?: string;
username?: string;
roles: Array<{
id: string;
name: string;
identifier: string;
}>;
organizerId?: string;
merchantId?: string;
iat: number; // Issued at
exp: number; // Expiration
}Cach su dung:
const { currentUser } = useRequestContext();
console.log(currentUser.userId); // "user-123"
console.log(currentUser.email); // "user@example.com"
console.log(currentUser.organizerId); // "org-456"
console.log(currentUser.roles);
// [{ id: "...", name: "Admin", identifier: "998-admin" }]userId
Loi tat cho currentUser.userId:
const { userId } = useRequestContext();
await this.repository.updateById({
id: recordId,
data: { updatedBy: userId },
});roles
Mang cac chuoi dinh danh vai tro, trich xuat thong qua currentUser.roles.map(r => r.identifier):
const { roles } = useRequestContext();
// ["999-super-admin", "899-organizer-owner"]
if (roles.includes('899-organizer-owner')) {
// Allow organizer-specific actions
}isAlwaysAllowed
Kiem tra neu nguoi dung giu vai tro SUPER_ADMIN hoac ADMIN. Duoc tinh bang cach giao FixedUserRoles.ALWAYS_ALLOW_ROLES voi dinh danh vai tro cua nguoi dung:
// Implementation detail:
const isAlwaysAllowed =
intersection(Array.from(FixedUserRoles.ALWAYS_ALLOW_ROLES), roles).length > 0;
// FixedUserRoles.ALWAYS_ALLOW_ROLES = Set(['999-super-admin', '998-admin'])Cach su dung:
const { isAlwaysAllowed, roles } = useRequestContext();
// Skip detailed checks for admins
if (isAlwaysAllowed) {
return this.performAction();
}
// Otherwise, check specific permissions
if (!roles.includes('editor')) {
throw new ForbiddenError();
}useCountData
Gia tri boolean lay tu header HTTP X-Request-Count-Data (mac dinh la true). Kiem soat viec phan hoi mang duoc bao boc trong { data, count } hay tra ve mang thuan:
const { useCountData } = useRequestContext();
// true => responses are { data: [...], count: N }
// false => responses are plain [...]Client co the tat bao boc bang cach gui:
X-Request-Count-Data: falsenormalizeCountableData
Chuan hoa ket qua truy van danh sach. Dat cac header phan hoi Content-Range, X-Response-Format, va X-Response-Count-Data, sau do tra ve { data, count } hoac mang thuan tuy theo useCountData:
const { normalizeCountableData } = useRequestContext();
const result = await this.repository.find({ where, limit, offset });
const total = await this.repository.count({ where });
return normalizeCountableData({
data: result,
range: { start: offset, end: offset + result.length - 1, total },
});
// If useCountData=true: { data: [...], count: N }
// If useCountData=false: [...]
// Headers set: Content-Range: records 0-9/100formatResponse va formatArrayResponse
Cac phuong thuc tien loi goi context.json() voi hinh dang phu hop dua tren useCountData:
const { formatResponse, formatArrayResponse } = useRequestContext();
// Single item
return formatResponse({ data: product, count: 1 });
// If useCountData=true: json({ data: product, count: 1 })
// If useCountData=false: json(product)
// Array
return formatArrayResponse({ data: products, count: products.length });
// If useCountData=true: json({ data: products, count: N })
// If useCountData=false: json(products)
// Custom status code
return formatResponse({ data: created, count: 1 }, 201);Luong Ngu canh Yeu cau
Cac Mau Uy quyen
Kiem soat Truy cap Dua tren Vai tro
@service()
export class MerchantService {
async updateMerchant(id: string, data: UpdateMerchantDto): Promise<TMerchant> {
const { isAlwaysAllowed, roles, currentUser } = useRequestContext();
// Super admins can update any merchant
if (isAlwaysAllowed) {
return this.merchantRepository.updateById({ id, data });
}
// Organizer owners can update their own merchants
if (roles.includes('899-organizer-owner')) {
const merchant = await this.merchantRepository.findById({ id });
if (merchant.organizerId !== currentUser.organizerId) {
throw new ForbiddenError('Cannot update merchant from another organization');
}
return this.merchantRepository.updateById({ id, data });
}
throw new ForbiddenError('Insufficient permissions');
}
}Kiem tra Quyen so huu Tai nguyen
@service()
export class SaleOrderService {
async getOrderDetails(orderId: string): Promise<TSaleOrder> {
const { userId, isAlwaysAllowed } = useRequestContext();
const order = await this.saleOrderRepository.findById({ id: orderId });
// Admins can see all orders
if (isAlwaysAllowed) {
return order;
}
// Regular users can only see their own orders
if (order.customerId !== userId) {
throw new ForbiddenError('Cannot access order from another user');
}
return order;
}
}Tham chieu Vai tro Nguoi dung Co dinh
export class FixedUserRoles {
// System-level roles
static readonly SUPER_ADMIN = '999-super-admin';
static readonly ADMIN = '998-admin';
static readonly OPERATOR = '997-operator';
// Organization-level roles
static readonly ORGANIZER_OWNER = '899-organizer-owner';
static readonly EMPLOYEE = '898-employee';
// Auto-allow set (used by isAlwaysAllowed)
static readonly ALWAYS_ALLOW_ROLES = new Set([
FixedUserRoles.SUPER_ADMIN,
FixedUserRoles.ADMIN,
]);
// Priority codes (higher = more privilege)
static readonly PRIORITY_CODE = {
SUPER_ADMIN: 999,
ADMIN: 998,
OPERATOR: 997,
ORGANIZER_OWNER: 899,
EMPLOYEE: 898,
};
}Cac Mau Nhat ky Kiem toan
Tao voi Kiem toan
async createProduct(data: CreateProductDto): Promise<TProduct> {
const { userId } = useRequestContext();
return this.productRepository.create({
data: {
id: IdGenerator.getInstance().nextId(),
...data,
createdBy: userId,
createdAt: new Date(),
},
});
}Cap nhat voi Kiem toan
async updateProduct(id: string, data: UpdateProductDto): Promise<TProduct> {
const { userId } = useRequestContext();
return this.productRepository.updateById({
id,
data: {
...data,
updatedBy: userId,
updatedAt: new Date(),
},
});
}Xoa Mem voi Kiem toan
async deleteProduct(id: string): Promise<void> {
const { userId } = useRequestContext();
await this.productRepository.updateById({
id,
data: {
deletedBy: userId,
deletedAt: new Date(),
},
});
}Xu ly Loi
Thieu Ngu canh
Neu useRequestContext() duoc goi ben ngoai vong doi yeu cau HTTP (vi du: trong cong viec nen hoac khi khoi dong ung dung), no se nem loi:
try {
const { userId } = useRequestContext();
} catch (error) {
// [useRequestContext] Request context is undefined.
}Xu ly Du phong Cho Cong viec Nen
function safeGetContext() {
try {
return useRequestContext();
} catch {
// In non-request context (e.g., background job, event handler)
return {
userId: 'system',
roles: ['system'],
isAlwaysAllowed: true,
currentUser: null,
context: null,
};
}
}Tich hop Controller
Ham useRequestContext() dua vao middleware xac thuc da dien thong tin CURRENT_USER vao ngu canh Hono. Luon su dung @authenticate tren cac route controller:
@controller({ basePath: '/products' })
export class ProductController {
constructor(private productService: ProductService) {}
@post('/')
@authenticate(['jwt'])
async create(@body() data: CreateProductDto): Promise<TProduct> {
// useRequestContext() is safe to call in the service layer
return this.productService.createProduct(data);
}
@get('/:id')
@authenticate(['jwt', 'basic'])
async getById(@param('id') id: string): Promise<TProduct> {
return this.productService.getProduct(id);
}
}Kiem thu
Mock Ngu canh Yeu cau
import { useRequestContext } from '@nx/core/utilities';
jest.mock('@nx/core/utilities', () => ({
useRequestContext: jest.fn(),
}));
describe('ProductService', () => {
beforeEach(() => {
(useRequestContext as jest.Mock).mockReturnValue({
userId: 'test-user-123',
roles: ['899-organizer-owner'],
isAlwaysAllowed: false,
useCountData: true,
currentUser: {
userId: 'test-user-123',
organizerId: 'test-org-456',
roles: [{ identifier: '899-organizer-owner' }],
},
});
});
it('creates product with audit trail', async () => {
const result = await productService.createProduct({ name: 'Test' });
expect(result.createdBy).toBe('test-user-123');
});
});Thuc hanh Tot nhat
1. Kiem tra Uy quyen Som
// Correct -- check at start of method
async updateProduct(id: string, data: UpdateProductDto) {
const { isAlwaysAllowed, roles } = useRequestContext();
if (!isAlwaysAllowed && !roles.includes('editor')) {
throw new ForbiddenError();
}
// Then proceed with business logic
}2. Su dung isAlwaysAllowed de Bo qua Admin
// Correct -- clean admin bypass
if (isAlwaysAllowed) {
return this.performAction();
}
// Avoid -- checking individual admin roles
if (roles.includes('999-super-admin') || roles.includes('998-admin')) {
// ...
}3. Bao gom Ngu canh trong Log
async processOrder(orderId: string) {
const { userId } = useRequestContext();
this.logger.info('Processing order | orderId: %s | userId: %s', orderId, userId);
}