feat(fundtransfer+imps+mfa): fund transfer module, IMPS switch client, MFA log API
- Fund transfer: NEFT/RTGS/IMPS/internal strategies, limit resolution, bulk
transfer, verification tokens, suspense GL, recall/approval flow, jobs
- IMPS CBS switch client with crypto (JWT/envelope) and transaction domain
- MFA log read API (audit of MFA events)
- Liquibase migrations 0300-0311 for fund transfer / IMPS tables, permissions,
roles, and suspense account
- JobName, SecurityConfig, dependencies.gradle wiring
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
Brings TOTP / Google Authenticator MFA factor (device mapping at
/v1/mfa/totp/setup + /verify, login via existing twofactor pipeline),
DSC-2FA login UI fix, Individual-only filter on guardian/nominee/family
link pickers, Title Case normalization on client names, and migration
0296 unique nominee per account type.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
# .claude/settings.local.json
# fineract-provider/logs/fineract.log
Backend:
- New dependency dev.samstevens.totp:totp:1.7.1 (bundles ZXing for QR PNG).
- TotpService: secret/QR/otpauth-URL/verify primitives. SHA1, 6 digits, 30s —
the standard combo every consumer authenticator app accepts unconfigured.
Issuer label is configurable via FINERACT_MFA_TOTP_ISSUER, defaults to
"CBSnexora".
- Two-step device mapping driven by TotpEnrollmentService:
* POST /v1/mfa/totp/setup — generate secret, upsert m_user_mfa_factor row
as factor_type=TOTP/verified=false, return {secret, qrDataUri, otpAuthUrl,
issuer, factorId}. Refuses to overwrite an already-verified-and-active
factor so an accidental click can't unbind the user's working device.
* POST /v1/mfa/totp/verify — body {code}; verify against the stored secret
and flip verified=true on success.
- Login wiring in MfaFactorBackedTwoFactorServiceImpl:
* getDeliveryMethodsForUser surfaces verified TOTP factors as
{name:"totp", target:"Google Authenticator"} so the picker offers it
alongside SMS/EMAIL.
* createNewOTPToken("totp") creates a no-op OTPRequest (no LincPay/SMTP
round-trip) carrying the factor id in the token field so validate can
look up the secret.
* createAccessTokenFromOTP branches on channel — TOTP path verifies code
vs factor.secret with totp library (sliding 1-step window) and mints
a TFAccessToken with the same lifetime + extended-token logic as OTP.
* Defence-in-depth: re-checks at validate time that the factor still
belongs to the user, is verified, and not disabled.
- TwoFactorConstants.TOTP_DELIVERY_METHOD_NAME = "totp".
- SecurityConfig: /api/*/mfa/totp/** is fullyAuthenticated() (exempt from
the TFA gate so users can enrol before they can satisfy 2FA).
UI changes pending (see chat): add a "Google Authenticator" tile on the
MFA Factors settings page that calls /setup → shows QR + secret → calls
/verify; extend two-factor-authentication.component.ts iconFor() and copy
for the new "totp" delivery method. Existing requestOTP / validateOTP
pipeline handles the login flow transparently.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- [x] Create fineract-investor module for build
- [x] modify tenant liquibase loader to using module based liquibase processing
- [x] sample liquibase into the fineract-investor module
- [x] Move NewCommandSourceHandler and dependencies to the fineract-core