- Login 2FA via @Primary MfaFactorBackedTwoFactorServiceImpl (replaces legacy) - LincPay SMS OTP and SMTP email OTP with auto-promote to m_user_mfa_factor - Reusable EmailSenderService + EmailTemplateService (CBSnexora branding) - Admin OTP audit viewer (GET /v1/otp-logs) with paging, search, masking - Self-service MFA factor API at /v1/mfa/factors - READ_OTP_LOG permission + OTP Log Auditor role + bankadmin direct grant - JsonAuthenticationEntryPoint suppresses Basic-auth popup for SPA paths - TwoFactorAuthenticationFilter grants TWOFACTOR_AUTHENTICATED when no challenge - Soft-delete + audit columns + deletion audit + version on client tables - CIF number generator (TTYYYYNNNNNN format) - DocumentNotFoundExceptionMapper to silence noisy WARN logs on missing images - Liquibase migrations 0278-0291 (MFA, OTP log, audit/soft-delete, role wiring) Note: application.properties intentionally not committed — contains local DB and Gmail App Password fallbacks. Set SPRING_MAIL_PASSWORD, FINERACT_DEFAULT_TENANTDB_PWD, FINERACT_HIKARI_PASSWORD as env vars in prod. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
201 lines
9.1 KiB
JSON
201 lines
9.1 KiB
JSON
{
|
|
"info": {
|
|
"name": "CBSnexora — Login OTP (Mobile + Email)",
|
|
"description": "Send and verify login OTPs via SMS (LincPay) and email. The `Send OTP` requests automatically capture the returned `otpId` into a collection variable so the matching `Verify OTP` request works in a single click.\n\n## Setup\n\n1. Open the collection's **Variables** tab and set:\n - `baseUrl` — e.g. `https://localhost:8443/fineract-provider/api/v1`\n - `tenantId` — e.g. `default`\n - `username` — the user whose registered mobile/email should receive the OTP\n2. Run **Send Mobile OTP** (or **Send Email OTP**), check the SMS/email for the 6-digit code, paste it into the matching **Verify** request's body, and run.\n\n## Endpoints\n\n- `POST /v1/login/otp/generate` — sends OTP via LincPay SMS\n- `POST /v1/login/otp/verify` — verifies the SMS OTP\n- `POST /v1/login/otp/email/generate` — sends OTP via SMTP email (CBSnexora-branded)\n- `POST /v1/login/otp/email/verify` — verifies the email OTP\n\nAll four endpoints are public (no auth header needed). Tenant header is still required.",
|
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
|
},
|
|
"item": [
|
|
{
|
|
"name": "Mobile OTP (LincPay SMS)",
|
|
"item": [
|
|
{
|
|
"name": "Send Mobile OTP",
|
|
"event": [
|
|
{
|
|
"listen": "test",
|
|
"script": {
|
|
"type": "text/javascript",
|
|
"exec": [
|
|
"// Capture otpId so the matching Verify request works without copy/paste",
|
|
"pm.test('Status 200', function () { pm.response.to.have.status(200); });",
|
|
"var json = pm.response.json();",
|
|
"if (json && json.otpId) {",
|
|
" pm.collectionVariables.set('mobileOtpId', json.otpId);",
|
|
" console.log('Saved mobileOtpId =', json.otpId);",
|
|
"}",
|
|
"pm.test('success flag is true', function () {",
|
|
" pm.expect(json.success).to.eql(true, json.message || 'OTP send failed');",
|
|
"});"
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{ "key": "Content-Type", "value": "application/json" },
|
|
{ "key": "Fineract-Platform-TenantId", "value": "{{tenantId}}" }
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"username\": \"{{username}}\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/login/otp/generate",
|
|
"host": ["{{baseUrl}}"],
|
|
"path": ["login", "otp", "generate"]
|
|
},
|
|
"description": "Sends a 6-digit OTP via LincPay SMS to the user's registered mobile.\n\nMobile is fetched server-side from `m_appuser.mobile_no` based on the username — the UI does not need to pass it.\n\nResponse stores `otpId` and `otpReference` in `m_otp_log`. The `otpId` is auto-saved to the `mobileOtpId` collection variable for the verify step."
|
|
}
|
|
},
|
|
{
|
|
"name": "Verify Mobile OTP",
|
|
"event": [
|
|
{
|
|
"listen": "test",
|
|
"script": {
|
|
"type": "text/javascript",
|
|
"exec": [
|
|
"pm.test('Status 200', function () { pm.response.to.have.status(200); });",
|
|
"var json = pm.response.json();",
|
|
"pm.test('verified flag is true', function () {",
|
|
" pm.expect(json.verified).to.eql(true, json.message || 'verification failed');",
|
|
"});"
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{ "key": "Content-Type", "value": "application/json" },
|
|
{ "key": "Fineract-Platform-TenantId", "value": "{{tenantId}}" }
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"otpId\": {{mobileOtpId}},\n \"otp\": \"123456\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/login/otp/verify",
|
|
"host": ["{{baseUrl}}"],
|
|
"path": ["login", "otp", "verify"]
|
|
},
|
|
"description": "Verifies the SMS OTP delivered by LincPay. Replace `123456` with the OTP you received on your phone before sending.\n\nThe `otpId` is auto-populated from the previous `Send Mobile OTP` request via the `mobileOtpId` collection variable.\n\nVerification is delegated to LincPay's `/verify-mobile-otp` endpoint; the audit row in `m_otp_log` is updated with the result."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Email OTP (SMTP)",
|
|
"item": [
|
|
{
|
|
"name": "Send Email OTP",
|
|
"event": [
|
|
{
|
|
"listen": "test",
|
|
"script": {
|
|
"type": "text/javascript",
|
|
"exec": [
|
|
"pm.test('Status 200', function () { pm.response.to.have.status(200); });",
|
|
"var json = pm.response.json();",
|
|
"if (json && json.otpId) {",
|
|
" pm.collectionVariables.set('emailOtpId', json.otpId);",
|
|
" console.log('Saved emailOtpId =', json.otpId);",
|
|
"}",
|
|
"pm.test('success flag is true', function () {",
|
|
" pm.expect(json.success).to.eql(true, json.message || 'OTP send failed');",
|
|
"});"
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{ "key": "Content-Type", "value": "application/json" },
|
|
{ "key": "Fineract-Platform-TenantId", "value": "{{tenantId}}" }
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"username\": \"{{username}}\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/login/otp/email/generate",
|
|
"host": ["{{baseUrl}}"],
|
|
"path": ["login", "otp", "email", "generate"]
|
|
},
|
|
"description": "Generates a 6-digit OTP, stores its SHA-256 hash in `m_otp_log`, and emails the plaintext OTP to the user's registered email (`m_appuser.email`) using the CBSnexora-branded HTML template.\n\nThe `otpId` is auto-saved to the `emailOtpId` collection variable for the verify step.\n\n**Pre-flight**: ensure `SPRING_MAIL_HOST`, `SPRING_MAIL_USERNAME`, `SPRING_MAIL_PASSWORD` env vars are populated. If SMTP is not configured, the response will be `{\"success\": false, \"message\": \"OTP send failed\"}` and the audit row marked `FAILED`."
|
|
}
|
|
},
|
|
{
|
|
"name": "Verify Email OTP",
|
|
"event": [
|
|
{
|
|
"listen": "test",
|
|
"script": {
|
|
"type": "text/javascript",
|
|
"exec": [
|
|
"pm.test('Status 200', function () { pm.response.to.have.status(200); });",
|
|
"var json = pm.response.json();",
|
|
"pm.test('verified flag is true', function () {",
|
|
" pm.expect(json.verified).to.eql(true, json.message || 'verification failed');",
|
|
"});"
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [
|
|
{ "key": "Content-Type", "value": "application/json" },
|
|
{ "key": "Fineract-Platform-TenantId", "value": "{{tenantId}}" }
|
|
],
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"otpId\": {{emailOtpId}},\n \"otp\": \"123456\"\n}"
|
|
},
|
|
"url": {
|
|
"raw": "{{baseUrl}}/login/otp/email/verify",
|
|
"host": ["{{baseUrl}}"],
|
|
"path": ["login", "otp", "email", "verify"]
|
|
},
|
|
"description": "Verifies the email OTP. Replace `123456` with the code from the email before sending.\n\nVerification is local: the submitted OTP is SHA-256 hashed and compared against the stored hash. On success the audit row is marked `VERIFIED`; failures increment `attempts` and lock at `max-attempts` (default 5)."
|
|
}
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"variable": [
|
|
{
|
|
"key": "baseUrl",
|
|
"value": "https://localhost:8443/fineract-provider/api/v1",
|
|
"type": "string",
|
|
"description": "Fineract API base URL including the version segment (e.g. /api/v1)."
|
|
},
|
|
{
|
|
"key": "tenantId",
|
|
"value": "default",
|
|
"type": "string",
|
|
"description": "Tenant identifier sent in the `Fineract-Platform-TenantId` header."
|
|
},
|
|
{
|
|
"key": "username",
|
|
"value": "alice",
|
|
"type": "string",
|
|
"description": "Username of the AppUser whose registered mobile / email will receive the OTP."
|
|
},
|
|
{
|
|
"key": "mobileOtpId",
|
|
"value": "",
|
|
"type": "string",
|
|
"description": "Auto-populated from `Send Mobile OTP` response — used by the verify request."
|
|
},
|
|
{
|
|
"key": "emailOtpId",
|
|
"value": "",
|
|
"type": "string",
|
|
"description": "Auto-populated from `Send Email OTP` response — used by the verify request."
|
|
}
|
|
]
|
|
}
|