[SEC] Field-Level Authorization + Resource Ownership Guard
Priority: High
Difficulty: Hard
Estimated Effort: 3-4 days
Relevant Packages: OrbitStream_backend/, orbitstream_docs/
Labels: security, enhancement, priority:high
Requirements
1. Public vs Authenticated Response Filtering
- Create a
PublicSessionDto that strips sensitive fields: only returns id, url, amount, asset, status, expiresAt
- The public
GET /v1/checkout/sessions/:id returns PublicSessionDto
- The authenticated merchant endpoint returns the full
CheckoutSession object
- Use class-transformer
@Exclude() and @Expose() decorators, or manual field selection
2. ResourceOwnershipGuard
Create a reusable guard that verifies the authenticated merchant owns the resource:
// src/auth/resource-ownership.guard.ts
@Injectable()
export class ResourceOwnershipGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const merchantId = request.merchantId; // from JWT or API key
const resourceId = request.params.id;
// Look up resource and verify ownership
// Return true if owned, throw ForbiddenException if not
}
}
Apply to:
DELETE /merchants/me/api-keys/:id — verify key belongs to merchant
POST /v1/checkout/sessions/:id/cancel — verify session belongs to merchant
- All future merchant-scoped endpoints
3. Role-Based Access Control
- Add a
role column to the merchants table: admin | merchant | viewer (default: merchant)
- Create a
RolesGuard that checks @Roles('admin') decorator
admin can access all merchants' data (platform operators)
merchant can access only their own data (default)
viewer can read but not write (for team members)
- Create a
@Roles() decorator
4. Audit Logging
- Log all authorization failures: who tried to access what, from which IP
- Create an
audit_logs table: id, merchant_id, action, resource_type, resource_id, ip_address, user_agent, created_at
- Log: failed auth attempts, successful logins, resource access denials, sensitive operations (API key generation, webhook changes)
5. Input Sanitization
- Validate
walletAddress format: must start with G, be exactly 56 characters, base32-encoded
- Validate
email format with RFC 5322 compliant regex
- Validate
webhookUrl format: must be HTTPS in production
- Validate
amount is positive and within reasonable bounds (0 < amount < 1,000,000)
6. Testing
- Unit test: ResourceOwnershipGuard allows owner, denies non-owner
- Unit test: RolesGuard allows admin, denies viewer on write endpoints
- Unit test: PublicSessionDto excludes sensitive fields
- Integration test: merchant A cannot cancel merchant B's session
- Integration test: viewer role cannot generate API keys
- Integration test: admin can access any merchant's data
[SEC] Field-Level Authorization + Resource Ownership Guard
Priority: High
Difficulty: Hard
Estimated Effort: 3-4 days
Relevant Packages:
OrbitStream_backend/,orbitstream_docs/Labels:
security,enhancement,priority:highRequirements
1. Public vs Authenticated Response Filtering
PublicSessionDtothat strips sensitive fields: only returnsid,url,amount,asset,status,expiresAtGET /v1/checkout/sessions/:idreturnsPublicSessionDtoCheckoutSessionobject@Exclude()and@Expose()decorators, or manual field selection2. ResourceOwnershipGuard
Create a reusable guard that verifies the authenticated merchant owns the resource:
Apply to:
DELETE /merchants/me/api-keys/:id— verify key belongs to merchantPOST /v1/checkout/sessions/:id/cancel— verify session belongs to merchant3. Role-Based Access Control
rolecolumn to themerchantstable:admin|merchant|viewer(default:merchant)RolesGuardthat checks@Roles('admin')decoratoradmincan access all merchants' data (platform operators)merchantcan access only their own data (default)viewercan read but not write (for team members)@Roles()decorator4. Audit Logging
audit_logstable:id, merchant_id, action, resource_type, resource_id, ip_address, user_agent, created_at5. Input Sanitization
walletAddressformat: must start withG, be exactly 56 characters, base32-encodedemailformat with RFC 5322 compliant regexwebhookUrlformat: must be HTTPS in productionamountis positive and within reasonable bounds (0 < amount < 1,000,000)6. Testing