feat: field-level auth, resource ownership, RBAC, audit logging, input sanitization#25
Merged
oomokaro1 merged 8 commits intoJun 20, 2026
Conversation
Add merchant_role enum (admin/merchant/viewer) and role column to merchants table. Add audit_logs table for tracking authorization failures, access denials, and sensitive operations with merchant_id, action, resource_type, ip_address, and user_agent columns.
Implement reusable ResourceOwnershipGuard with @ResourceOwner decorator for verifying merchant ownership of resources (api_key, checkout_session, merchant). Implement RolesGuard with @roles() decorator for role-based access control. Admin can access all resources, merchant owns their data, viewer can read only. Both guards log authorization failures via AuditService.
Create global AuditService for logging authorization failures, access denials, and sensitive operations (API key generation/revocation, webhook updates, session cancellations). AuditModule is globally available across all modules.
Add PublicSessionDto to expose only safe fields (id, url, amount, asset, status, expiresAt) from checkout sessions. Add Stellar address regex validation to RegisterMerchantDto. Add @max(1000000) bound to checkout amount. Add HTTPS-only validation for webhookUrl in production. Add enum validation for API key environment field.
Apply ResourceOwnershipGuard to DELETE /merchants/me/api-keys/:id and POST /v1/checkout/sessions/:id/cancel endpoints. Apply RolesGuard with @roles('admin', 'merchant') to all write endpoints. Viewer role denied from generateApiKey, revokeApiKey, setWebhook, createSession, cancelSession, and other mutation endpoints. Update public GET /v1/checkout/sessions/:id to return PublicSessionDto, stripping merchantId, receivingAccount, memo, and metadata fields. Integrate AuditService into MerchantsService and CheckoutService for logging sensitive operations. Add AuditModule to AppModule, MerchantsModule, and CheckoutModule.
Add ResourceOwnershipGuard tests: allows owner, denies non-owner, denies missing resource, denies no merchant context. Add RolesGuard tests: allows admin everywhere, allows merchant on own data, denies viewer on write endpoints. Add CheckoutService tests: verifies PublicSessionDto strips sensitive fields, audit logging on session cancellation. Add MerchantsService tests: verifies audit logging on registration, API key generation/revocation, and webhook updates. Fix webhook-module.spec.ts to mock AuditService dependency injection.
oomokaro1
requested changes
Jun 20, 2026
oomokaro1
left a comment
Contributor
There was a problem hiding this comment.
This PR fully implements issue #12 (field-level auth, resource ownership, RBAC, audit logging, input sanitization) with clean architecture and good test coverage.
Blocking Issues
CI lint failed — 10 errors in new files from this PR:
src/checkout/checkout.service.spec.ts:2— unused importBadRequestExceptionsrc/checkout/checkout.service.spec.ts:20— unused variableauditServicesrc/merchants/merchants.service.spec.ts:22— unused variableauditService- 7 prettier formatting errors across
resource-ownership.guard.ts,resource-ownership.guard.spec.ts,roles.guard.spec.ts,checkout.service.spec.ts,merchants.service.spec.ts
Fix: npm run lint -- --fix then remove the 3 unused imports/variables manually.
Code Issues
SetWebhookDtoHTTPS validation usesgroups: ['production']but no mechanism activates that group — validation may never trigger. Consider using@ValidateIf(() => process.env.NODE_ENV === 'production')with the@Matcheswithout groups.
What's Good
- Complete coverage of all issue #12 requirements
PublicSessionDtocorrectly strips sensitive fieldsResourceOwnershipGuardandRolesGuardare reusable and well-tested@Global()AuditModulepattern is clean and intentional- Audit logging silently drops DB errors — never breaks user requests
- Default
merchantrole requires no migration
- Remove unused BadRequestException import from checkout.service.spec.ts - Remove unused auditService variable from checkout.service.spec.ts and merchants.service.spec.ts - Fix SetWebhookDto: remove unused groups parameter from @matches decorator; @ValidateIf already handles the production check - Run prettier to fix formatting issues across new files
Run eslint --fix on guard and spec files to resolve formatting mismatches between local prettier and CI environment.
Contributor
Author
|
@oomokaro1 All feedback has been addressed:
CI is now passing. Ready for re-review. |
oomokaro1
approved these changes
Jun 20, 2026
oomokaro1
left a comment
Contributor
There was a problem hiding this comment.
All lint errors from the previous review have been fixed. CI is green, no merge conflicts, and all issue #12 requirements are fully implemented.
What changed since last review
- Removed unused
BadRequestExceptionimport andauditServicevariables in test files - Fixed prettier formatting across guard specs and service files
- Fixed
SetWebhookDtoHTTPS validation (removed unusedgroupsparameter)
What's good
- Clean, well-tested implementation of field-level auth, resource ownership, RBAC, audit logging, and input sanitization
@Global()AuditModulepattern is intentional and clean- Audit logging silently drops DB errors — never breaks user requests
- Default
merchantrole requires no migration
LGTM 👍
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #12
Type of Change
Summary
This PR implements a layered security model for the OrbitStream backend: field-level response filtering, resource ownership guards, role-based access control, audit logging, and input sanitization.
The public checkout endpoint previously exposed sensitive fields (
merchantId,receivingAccount,memo,metadata) to unauthenticated callers. Write endpoints lacked ownership verification, and there was no RBAC or audit trail for authorization decisions.Motivation / Context
Fixes #12
The backend had no field-level authorization, resource ownership verification, or role-based access control. An authenticated merchant could potentially access or manipulate resources belonging to other merchants. The public checkout endpoint leaked internal fields. These gaps could allow cross-tenant data exposure and unauthorized resource manipulation.
Detailed Changes
1. PublicSessionDto + Response Filtering
PublicSessionDtoexposing only safe fields:id,url,amount,asset,status,expiresAtgetSession()inCheckoutServiceto returnPublicSessionDtoviatoPublicSession()methodGET /v1/checkout/sessions/:idnow stripsmerchantId,receivingAccount,memo,metadata,successUrl,cancelUrl,assetIssuer2. ResourceOwnershipGuard
ResourceOwnershipGuardimplementingCanActivate@ResourceOwner('api_key'|'checkout_session'|'merchant')decorator for metadata-driven ownership checksrequest.merchantIdDELETE /merchants/me/api-keys/:idandPOST /v1/checkout/sessions/:id/cancel3. Role-Based Access Control
merchant_roleenum (admin|merchant|viewer) androlecolumn to merchants table (default:merchant)RolesGuardwith@Roles()decoratoradminrole bypasses all RBAC checksviewerrole denied from all write endpoints (generateApiKey, revokeApiKey, setWebhook, createSession, cancelSession, updateProfile, setCorsOrigins)MerchantsController, method level onCheckoutController4. Audit Logging
AuditService(globally available via@Global()AuditModule)audit_logstable:id,merchant_id,action,resource_type,resource_id,ip_address,user_agent,details,created_atResourceOwnershipGuard(logs access denials and resource-not-found)RolesGuard(logs insufficient role errors)MerchantsService(logs registration, API key gen/revoke, webhook updates)CheckoutService(logs session cancellations)5. Input Sanitization
/^G[A-Z0-9]{55}$/) toRegisterMerchantDto@Max(1000000)to checkout session amountwebhookUrlin production environments@Matches(/^(testnet|mainnet)$/)enum validation toGenerateApiKeyDto@MaxLength(255)tobusinessName6. Module Wiring
AuditModuletoAppModule,MerchantsModule,CheckoutModulewebhook-module.spec.tsto mockAuditServicedependencyCurrent Behavior vs. New Behavior
Before:
GET /v1/checkout/sessions/:idreturned full session includingmerchantId,receivingAccount,memo,metadataAfter:
Testing
ResourceOwnershipGuardallows owner, denies non-owner withForbiddenException, logs access deniedRolesGuardallows admin on all routes, allows merchant on own data, denies viewer on write endpointsPublicSessionDtoconfirms sensitive fields (merchantId,receivingAccount,memo,metadata) excluded from serializationCheckoutService.getSession()returns only public fieldsAuditServiceinserts log entries and silently handles DB errorsMerchantsServicelogs sensitive operations (register, API key gen/revoke, webhook update)tsc --noEmit)Breaking Changes
No
All existing API contracts are preserved. The only behavioral change is that the public checkout endpoint now returns fewer fields (previously leaked internal fields). Clients that relied on
receivingAccount,memo, ormerchantIdfrom the public endpoint will need to use the authenticated merchant endpoint instead.Risks and Rollback
Risks:
rolecolumn defaults tomerchant, so existing merchants retain their current access level. No migration needed.@Global()AuditModuleis available to all modules; if a future module forgets to mock it in tests, test compilation will fail (intentional — forces explicit dependency declaration).Rollback:
AuditModulefromAppModuleimports@UseGuards(ResourceOwnershipGuard)and@UseGuards(RolesGuard)from controllersgetSession()to return full session objectaudit_logstable andmerchant_roleenumChecklist
Self-Review
console.log,print,debugger, commented-out blocks)Testing
CI / Pipeline
Documentation
Changelog
Security
Performance
Reviewer Notes
src/auth/resource-ownership.guard.ts— theresolveOwnerIdfunction does per-resource-type DB lookups. For high-traffic scenarios, consider caching ownership lookups.RolesGuardreads the merchant's role from the DB on every request. In production, consider caching the role in the JWT payload or a short-lived Redis cache.@Global()AuditModulepattern is intentional — audit logging should be available everywhere without explicit imports.