Skip to content

Data Access Controls

Document ID: ACP-001.3 Version: 1.0 Classification: Internal / Regulator-Shareable Effective Date: March 2026 Next Review: September 2026 Parent Document: ACP-001 — Access Control & Permissions


1. Overview

Beyond role-based permissions, Keshless enforces data-level access controls that ensure users can only access data they are authorized to see. This document covers ownership-based access, tenant isolation, KYC-gated operations, wallet security, and audit logging.

2. Ownership-Based Access

2.1 Principle

End users can only access their own data. Admin users can access any user's data (subject to their role permissions). This is enforced by the checkOwnershipOrAdmin() middleware.

2.2 Ownership Check Logic

Request to access resource with ID = :id

    ├── Is requester ADMIN, SUPER_ADMIN, or ADMIN_EMPLOYEE?
    │   └── Yes → Access granted (subject to role permissions)

    └── Does requester's userId match the resource owner?
        ├── Yes → Access granted
        └── No → 403 Forbidden

2.3 Implementation

The checkOwnershipOrAdmin(paramName) middleware extracts the resource identifier from the route parameter and compares it to the authenticated user's ID. This pattern is applied to:

  • User profile endpoints (/api/users/:id)
  • Transaction history (/api/users/:id/transactions)
  • Wallet operations (/api/wallets/:userId)
  • Verification status (/api/verifications/:userId)

3. Vendor Data Isolation

3.1 Vendor Scoping

Vendor data is strictly isolated — each vendor can only access their own business data:

Data TypeScoping Mechanism
TransactionsFiltered by vendorId from JWT
Sub-usersOnly sub-users belonging to parent vendor
Financial reportsAggregated per-vendor
Customer dataOnly customers who transacted with the vendor
Integration API keysScoped to the vendor that created them

3.2 Vendor Sub-User Scoping

Vendor sub-users inherit their parent vendor's data scope but may be further restricted by their permission set:

  • A cashier can create transactions but cannot view all transaction history
  • An accountant can view financial reports but cannot create transactions
  • A manager has broad access but cannot manage the vendor profile

3.3 Enforcement

The requireOwnVendor() middleware ensures that vendor API calls can only access the vendor's own data by comparing req.user.vendorId from the JWT against the requested vendor ID in the route or request body.

4. Service Center Isolation

4.1 Binding Model

Service center staff are bound to a specific service center via the serviceCenterId field on their AdminEmployee record. This binding is permanent (cannot be changed without admin intervention) and enforced at every request.

4.2 Scoping Rules

RuleEnforcement
Staff can only see their own SC's balancereq.serviceCenterId filters all queries
Staff can only process transactions at their SCTransaction records tagged with serviceCenterId
SC Manager can only manage staff at their SCAgent CRUD operations scoped by serviceCenterId
Inactive SC blocks all operationsrequireServiceCenterStaff() checks SC status = ACTIVE

4.3 Service Center Middleware

The requireServiceCenterStaff() middleware performs two checks:

  1. Staff assignment: Confirms the admin employee has a serviceCenterId assigned
  2. SC active status: Verifies the service center record has status ACTIVE

If either check fails, the request is rejected with a 403 response.

The attachServiceCenterIfStaff() middleware is a softer variant — it attaches req.serviceCenterId if the user is SC staff but does not reject non-SC users. This is used on shared endpoints accessible by both SC staff and regular admins.

5. KYC-Gated Access

5.1 Verification Status Model

User access to financial operations is gated by their KYC verification status:

StatusDescriptionAccess Level
PENDINGInitial state, no KYC submittedBasic account, limited operations
IN_PROGRESSKYC documents submitted, awaiting reviewSame as PENDING
VERIFIEDKYC approvedFull access to all wallet operations
REJECTEDKYC failedRestricted — must resubmit
NEEDS_REVIEWFlagged for manual compliance reviewOperations may be suspended

5.2 Operation Gating

OperationRequires KYC VERIFIED
Receive money
View balance
Send money (P2P)
Withdraw cash
Pay bills
NFC payments
Higher transaction limits

5.3 Admin KYC Permissions

Only admins with specific permissions can modify KYC status:

ActionRequired Permission
View KYC submissionsVIEW_VERIFICATIONS
Approve KYCAPPROVE_VERIFICATIONS
Reject KYCREJECT_VERIFICATIONS
Service center KYC verificationSC_VERIFY_USER_KYC

6. Wallet Security

6.1 Dual Balance Model

Every user wallet has two balance fields:

BalanceDescriptionUsage
walletBalance (withdrawable)Funds that can be withdrawn to cashRegular deposits, P2P receives
nonWithdrawableBalanceFunds restricted from withdrawalPromotional credits, refunds with conditions

6.2 Cascade Deduction

When a user makes a payment, balances are deducted in order:

  1. Withdrawable balance deducted first
  2. Non-withdrawable balance used only if withdrawable is insufficient
  3. If combined balance is insufficient → transaction rejected

6.3 Wallet Access Rules

OperationWho Can PerformPermission Required
View own balanceUserAuthentication only
View any user's balanceAdminMANAGE_USER_WALLETS
Manual wallet adjustmentAdminMANUAL_ADJUSTMENTS
Process withdrawalAdminPROCESS_WITHDRAWALS
Approve withdrawalAdminAPPROVE_WITHDRAWALS
SC user topupSC StaffSC_TOPUP_USER
SC user withdrawalSC StaffSC_WITHDRAW_USER
Vendor topup userVendortopup_user (vendor permission)
Vendor withdraw userVendorwithdraw_user (vendor permission)

6.4 Transaction Limits

Transaction limits are configured per context and enforced at the API level:

Limit TypeConfigured ByApplies To
Per-transaction maxSystem configAll users
Daily transaction limitSystem configPer user
Per-role limitsAdmin configBy user role/KYC level
SC agent limitsSC ManagerPer service center agent
USSD limitsEnvironment configUSSD channel specifically

USSD Configured Limits (from environment):

ParameterDefault Value
USSD_MAX_TRANSFERE 10,000
USSD_MAX_WITHDRAWALE 5,000
USSD_MAX_AIRTIMEE 500
USSD_DAILY_LIMITE 50,000

7. Data Classification & Sensitive Field Handling

7.1 Data Classification

ClassificationExamplesAccess Level
RestrictedNational ID numbers, PINs, passwords, KYC imagesEncrypted; accessed only by authorized services
ConfidentialWallet balances, transaction history, phone numbersAuthenticated access; scoped by ownership
InternalSystem logs, audit trails, configurationAdmin access with appropriate permissions
PublicApp store listings, marketing contentNo access control

7.2 Sensitive Field Masking

The following fields are masked in application logs and error responses:

  • National ID numbers
  • Dates of birth
  • Passwords and PINs
  • KYC document images
  • Full wallet balances (shown only to authorized principals)
  • OTP codes

7.3 KYC Document Storage

KYC documents (ID photos, selfies, proof of address) are stored in GCP Cloud Storage with the following access controls:

ControlImplementation
Bucket accessPrivate (no public URLs)
Document accessSigned URLs with expiration
URL expiryTime-limited (configurable)
Who can viewAdmin with VIEW_VERIFICATIONS permission
Storage regioneurope-west1 (data residency)

8. Audit Trail

8.1 What Is Logged

All administrative actions and significant system events are captured in the audit log:

Event CategoryExamples
AuthenticationLogin, logout, failed login, password change
User managementCreate, edit, block, delete user
Vendor managementApprove, block, edit vendor
Financial operationsWithdrawal processing, manual adjustment, reversal
KYC operationsApprove, reject verification
AML operationsAlert management, SAR filing
System changesEmergency control activation, fee changes, limit changes
Admin managementCreate, edit, delete admin employee

8.2 Audit Log Fields

Each audit entry contains:

FieldDescription
TimestampISO 8601 with timezone
ActorUser ID and role of who performed the action
ActionWhat was done (CREATE, UPDATE, DELETE, etc.)
ResourceWhat was affected (type and ID)
ChangesBefore/after values for modifications
IP AddressSource IP of the request
HashSHA-256 hash for tamper detection

8.3 Hash Chain Integrity

Audit log entries are chained using SHA-256 hashes. Each entry's hash includes the previous entry's hash, creating an append-only, tamper-evident log. If any entry is modified or deleted, the chain breaks and the tampering is detectable.

8.4 Audit Log Access

PermissionAccess Level
VIEW_AUDIT_LOGSRead audit trail entries
No permission exists to modify or delete audit logsBy design — audit logs are immutable

Regulatory Significance: The immutable, hash-chained audit trail satisfies FATF Recommendation 11 (record keeping) and PCI DSS Requirement 10 (track and monitor access).

9. Database-Level Controls

9.1 Connection Security

ControlImplementation
Cloud SQL ProxyAll DB connections via authenticated proxy
Private IPDatabase not exposed to public internet
TLSAll connections encrypted in transit
CredentialsStored in GCP Secret Manager
Regioneurope-west1 (co-located with application)

9.2 Application-Level ORM

All database queries go through Prisma ORM, which provides:

  • Parameterized queries — prevents SQL injection
  • Type-safe queries — compile-time validation
  • Decimal precisionDecimal(15,2) for all monetary values
  • Soft deletes — where applicable, records are marked deleted rather than removed

Internal use only - Keshless Payment Platform