Skip to content

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

typescript
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 tinhKieuMo ta
contextContextDoi tuong ngu canh yeu cau Hono nguyen goc
currentUserIJWTTokenPayloadPayload JWT da giai ma day du
userIdstringLoi tat cho currentUser.userId
rolesstring[]Mang cac chuoi dinh danh vai tro trich xuat tu currentUser.roles
isAlwaysAllowedbooleantrue neu nguoi dung co vai tro SUPER_ADMIN hoac ADMIN
useCountDatabooleanCo 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?) => ResponseDinh dang phan hoi don le
formatArrayResponse<T>(result, statusCode?) => ResponseDinh dang phan hoi mang

Cach Su dung Co ban

typescript
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:

typescript
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:

typescript
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:

typescript
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):

typescript
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:

typescript
// 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:

typescript
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:

typescript
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: false

normalizeCountableData

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:

typescript
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/100

formatResponse va formatArrayResponse

Cac phuong thuc tien loi goi context.json() voi hinh dang phu hop dua tren useCountData:

typescript
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

typescript
@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

typescript
@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

typescript
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

typescript
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

typescript
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

typescript
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:

typescript
try {
  const { userId } = useRequestContext();
} catch (error) {
  // [useRequestContext] Request context is undefined.
}

Xu ly Du phong Cho Cong viec Nen

typescript
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:

typescript
@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

typescript
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

typescript
// 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

typescript
// 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

typescript
async processOrder(orderId: string) {
  const { userId } = useRequestContext();
  this.logger.info('Processing order | orderId: %s | userId: %s', orderId, userId);
}

Tai lieu Lien quan

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.