Repository navigation
Security: stop echoing rejected passwords, check the policy before sending, and let access be withdrawn - #817
Conversation
Fineract returns the value it rejected in the error's `args`, and this formatter appends
them to the message so a user can see what the platform objected to. For a password that
is a disclosure. Measured against a running Fineract, `POST /users` with a password the
policy refuses answers
{ "parameterName": "password",
"userMessageGlobalisationCode": "validation.msg.user.password.does.not.match.regexp",
"args": [ { "value": "<the submitted password>" }, { "value": "^(?!.*(.)\1)…" } ] }
and the `repeatPassword` entry beside it carries **both** values. The field is masked
precisely so the value is not readable on screen, over a shoulder, in a screenshot or in
a support ticket, and the toast undid all of that. Issue apache#752.
Args are now dropped for any parameter whose name contains `password`, `secret`, `pin`,
`token`, `credential` or `otp` — a substring test, so `repeatPassword` and `clientSecret`
are covered without enumerating the spellings the platform might use. Dropped entirely
rather than filtered for the value: the only other thing in them is the policy regex,
which no operator can act on, and the message beside it already states the rule in words.
The rules themselves still reach the user, and a non-sensitive parameter still carries its
args — a refused amount or date is exactly what that detail is for.
… users be withdrawn Two gaps on the Security screens, both measured against a running Fineract. ## The password is checked before it is sent (apache#753) Create User validated its two password fields for presence and nothing else: with different values in them Save stayed enabled, and the only feedback was a 400 in a toast — the same toast that printed the password in clear text until the previous commit. The rules are the tenant's own. `GET /passwordpreferences` answers the active policy and `/passwordpreferences/template` all three — `simple`, `secure` and `strong`, each with the description the platform words itself — so the description is shown as the field's helper text rather than restated here. What that endpoint does *not* answer is the regex it enforces: that appears only in the `args` of a rejected password, which carry the submitted password with them, so `core/security/password-policy.ts` mirrors the rules by `key`. Mirroring carries one obligation, and the fallback honours it: an unrecognised key gets the loosest rules, so a policy this code has not heard of cannot refuse a password the platform would have accepted. The form now names the rules a password still fails — "repeat no character twice in a row" for a password refused on its `ss` alone, which the platform's own message never says — reports a mismatch under the confirmation field, and holds Save until both are right. The profile screen's change-password form shares the same rules through the same adapter operation, so the two cannot drift. The password pair also moved out of the form model into signals. `[(ngModel)]="user().password"` mutates the object a signal holds without notifying it, so nothing derived from it would ever recompute — the trap that emptied the floating-rate editor in apache#815. The fields are hidden entirely when the platform is to mail a generated password, which it refuses to do if the request carries one. ## Access can be withdrawn (apache#755) Roles and Users offered create and edit only, so a departed officer's access could be cut only by editing their roles down to nothing, and a role created by mistake stayed in every picker for good. - a role can be disabled, re-enabled and deleted. `GET /roles` has always carried `disabled` — the stock `Self Service User` role answers `true` — and the screen now shows it as a status and acts on it. `GET /users/template` already filters disabled roles out of the options a new user can be given, so switching one off is enough to stop it being assigned again. - a user can be deleted, which is the only withdrawal the platform offers for an account. Never on the signed-in one: the platform allows that, and an administrator who deletes the account they are signed in with locks themselves out. Every action is gated on its permission (`DISABLE_ROLE`, `ENABLE_ROLE`, `DELETE_ROLE`, `DELETE_USER`) and confirmed first, destructively for the two deletes. Deleting a role the platform still refuses — `DELETE /roles/{id}` answers 403 `error.msg.role.associated.with.users.deleted` while any user holds it — reaches the user as "Role with identifier 34 associated with users", because `errorInterceptor` reports a domain-rule 403 in the platform's own words. Verified in the browser against a running instance, along with the status going ACTIVE → DISABLED → ACTIVE and both deletes.
9b356d9 to
41201a0
Compare
E2E — mocked backend🎭 E2E Tests✅ All green — 427 passed · 0 failed · 1 skipped, across 40 spec files in 9m 25s. By spec file
All 428 tests — click to expand
Slowest 10 — what the shard counts should be tuned against
📼 Download the HTML report, videos and traces — see the Generated by run 39260395538 from |
E2E — real Fineract🎭 E2E Tests✅ All green — 171 passed · 0 failed · 0 skipped · 2 flaky, across 50 spec files in 14m 24s.
|
| Spec | ✅ | ❌ | ⏭️ | 🔒 | Time |
|---|---|---|---|---|---|
| backend.setup.ts | 3 | 0 | 0 | 0 | 5.8s |
| accounting-closure-backend.spec.ts | 2 | 0 | 0 | 0 | 15.8s |
| batch-api-operations.spec.ts | 5 | 0 | 0 | 0 | 27.1s |
| center-servicing.spec.ts | 2 | 0 | 0 | 0 | 17.2s |
| charge-servicing-backend.spec.ts | 6 | 0 | 0 | 0 | 29.2s |
| client-legal-form-search.spec.ts | 1 | 0 | 0 | 0 | 4.7s |
| client-transfer.spec.ts | 4 | 0 | 0 | 0 | 47.2s |
| collection-sheet.spec.ts | 1 | 0 | 0 | 0 | 6.3s |
| deposit-account-servicing.spec.ts | 3 | 0 | 0 | 0 | 26.5s |
| deposit-lifecycle-backend.spec.ts | 4 | 0 | 0 | 0 | 23.2s |
| deposit-product-configuration.spec.ts | 3 | 0 | 0 | 0 | 16.6s |
| double-submit-backend.spec.ts | 2 | 0 | 0 | 0 | 11.6s |
| form-layout-reflow.spec.ts | 6 | 0 | 0 | 0 | 33.7s |
| group-membership.spec.ts | 3 | 0 | 0 | 0 | 49.8s |
| loan-account-actions.spec.ts | 3 | 0 | 0 | 0 | 24.0s |
| loan-approved-amount-revision.spec.ts | 3 | 0 | 0 | 0 | 19.6s |
| loan-charge-off.spec.ts | 2 | 0 | 0 | 0 | 20.9s |
| loan-flows-non-admin-roles.spec.ts | 8 | 0 | 0 | 0 | 1m 47s |
| loan-product-accounting.spec.ts | 1 | 0 | 0 | 0 | 33.8s |
| loan-repayment-gating.spec.ts | 2 | 0 | 0 | 0 | 10.3s |
| loan-servicing-rbac-backend.spec.ts | 4 | 0 | 0 | 0 | 11.2s |
| loan-servicing.spec.ts | 2 | 0 | 0 | 0 | 19.2s |
| login.spec.ts | 4 | 0 | 0 | 0 | 5.8s |
| oidc-login-backend.spec.ts | 2 | 0 | 0 | 0 | 5.5s |
| parity-screens.spec.ts | 8 | 0 | 0 | 0 | 44.1s |
| rbac-backend-restricted-user.spec.ts | 7 | 0 | 0 | 0 | 35.2s |
| rbac-dead-end-controls.spec.ts | 7 | 0 | 0 | 0 | 34.7s |
| rbac-disabled-flag-backend.spec.ts | 6 | 0 | 0 | 0 | 28.5s |
| rbac-multi-permission.spec.ts | 9 | 0 | 0 | 0 | 58.7s |
| rbac-office-scoped-user.spec.ts | 6 | 0 | 0 | 0 | 26.6s |
| bulk-import-entity-types.spec.ts | 1 | 0 | 0 | 0 | 10.2s |
| delinquency-configuration.spec.ts | 2 | 0 | 0 | 0 | 17.1s |
| full-demo.spec.ts | 1 | 0 | 0 | 0 | 58.4s |
| interest-rate-charts.spec.ts | 1 | 0 | 0 | 0 | 7.3s |
| loan-lifecycle.spec.ts | 4 | 0 | 0 | 0 | 1m 58s |
| loan-schedule-type.spec.ts | 3 | 0 | 0 | 0 | 33.7s |
| rbac-permissionless-user.spec.ts | 4 | 0 | 0 | 0 | 21.2s |
| report-mailing-job.spec.ts | 3 | 0 | 0 | 0 | 19.0s |
| report-parameter-backend.spec.ts | 4 | 0 | 0 | 0 | 22.8s |
| savings-flows-non-admin-roles.spec.ts | 7 | 0 | 0 | 0 | 1m 36s |
| savings-transaction-correction.spec.ts | 1 | 0 | 0 | 0 | 6.1s |
| savings-transaction-gating.spec.ts | 2 | 0 | 0 | 0 | 5.8s |
| share-account-servicing.spec.ts | 2 | 0 | 0 | 0 | 18.9s |
| share-product-accounting.spec.ts | 1 | 0 | 0 | 0 | 25.1s |
| teller-cash-management.spec.ts | 2 | 0 | 0 | 0 | 36.8s |
| tenant-date-defaults-backend.spec.ts | 1 | 0 | 0 | 0 | 6.0s |
| working-capital-loan-actions-backend.spec.ts | 3 | 0 | 0 | 0 | 22.1s |
| working-capital-product-accounting-backend.spec.ts | 2 | 0 | 0 | 0 | 20.8s |
| working-capital-rbac-backend.spec.ts | 2 | 0 | 0 | 0 | 7.6s |
| working-capital-servicing-backend.spec.ts | 6 | 0 | 0 | 0 | 35.5s |
All 171 tests — click to expand
backend.setup.ts
- ✅ seed backend reference data —
1.8s - ✅ seed backend reference data —
2.1s - ✅ seed backend reference data —
1.9s
accounting-closure-backend.spec.ts
- ✅ Accounting closures against Fineract › a closed period reads as Closed, and the platform refuses a posting inside it —
10.6s(retried 1×) - ✅ Accounting closures against Fineract › the form keeps the user on the page and names the reason when Fineract refuses —
5.2s
batch-api-operations.spec.ts
- ✅ Batch API Operations against Fineract › runs the sample batch scenario — create client, create loan, add and read back a charge —
5.6s - ✅ Batch API Operations against Fineract › shows a parse error instead of submitting when the batch input is not valid JSON —
6.9s - ✅ Batch API enclosingTransaction semantics against Fineract › rolls back the earlier steps when enclosingTransaction is true and a later step fails —
4.7s(retried 2×) - ✅ Batch API enclosingTransaction semantics against Fineract › does not roll back the earlier steps when enclosingTransaction is false and a later step fails —
5.0s - ✅ Batch API Operations on a mobile viewport against Fineract › the sample batch scenario is reachable and works by touch at mobile width —
4.8s
center-servicing.spec.ts
- ✅ Center servicing › a center is activated, staffed and given a group —
12.2s - ✅ Center servicing › notes are recorded against the center —
5.0s
charge-servicing-backend.spec.ts
- ✅ loan charge servicing › waives a loan charge, and the platform records the waiver —
6.0s - ✅ loan charge servicing › cannot reverse a waiver: the platform answers 500, not a business rule —
622ms - ✅ loan charge servicing › offers no Pay on a charge the platform will not let be paid —
5.1s - ✅ loan charge servicing › adjusts a loan charge to a smaller amount —
6.4s - ✅ savings charge servicing › pays a savings charge with the amount and due date the platform demands —
5.4s - ✅ savings charge servicing › carries an active flag, which is what gates Inactivate —
5.6s
client-legal-form-search.spec.ts
- ✅ Client list: legal form filtering › an entity client appears in the main client list —
4.7s
client-transfer.spec.ts
- ✅ Client transfer between offices › a proposed transfer is held until the destination accepts, and then the client moves —
11.8s - ✅ Client transfer between offices › a rejected transfer leaves the client on hold, and withdrawing is the way back —
12.2s - ✅ Client transfer between offices › a client can be transferred in one step when the user may act for both offices —
10.8s - ✅ Client staff assignment › an officer can be assigned and then removed —
12.3s
collection-sheet.spec.ts
- ✅ Collection Sheet › scopes staff to the office, sends the choice, and reports an empty sheet —
6.3s
deposit-account-servicing.spec.ts
- ✅ Term deposit account servicing › an account is approved, activated and closed before maturity —
11.3s - ✅ Term deposit account servicing › a deposit is recorded, listed, and reversed without leaving the list —
9.2s - ✅ Term deposit account servicing › an application can be rejected instead of approved —
6.0s
deposit-lifecycle-backend.spec.ts
- ✅ fixed deposit products › carry the rate the user typed, not a hidden default —
5.6s - ✅ an active fixed deposit account › shows its balance and maturity figures —
5.0s - ✅ an active fixed deposit account › can be closed into the client savings account —
7.4s - ✅ an active fixed deposit account › can have a charge added to it —
5.2s
deposit-product-configuration.spec.ts
- ✅ Deposit product configuration › a fixed deposit product survives being edited —
6.0s - ✅ Deposit product configuration › a recurring deposit product can be created at all —
4.7s - ✅ Deposit product configuration › a savings product carries its accounting configuration —
5.9s
double-submit-backend.spec.ts
- ✅ A double-click submits once › a repayment is recorded once, not twice —
5.1s - ✅ A double-click submits once › a client is created once, not twice —
6.5s
form-layout-reflow.spec.ts
- ✅ form layout at the narrow viewport › Create GL Account gives every field a usable width at 320px —
4.8s - ✅ form layout at the narrow viewport › Create Loan gives every field a usable width at 320px —
4.5s - ✅ form layout at the narrow viewport › Create Office gives every field a usable width at 320px —
5.1s - ✅ form layout at the narrow viewport › Create Staff gives every field a usable width at 320px —
4.7s - ✅ form layout at the narrow viewport › no page-level horizontal scroll at 320px —
9.2s - ✅ form layout at the wide viewport › Create GL Account still uses more than one column at 1280px —
5.4s
group-membership.spec.ts
- ✅ Group membership and lifecycle › a group is activated, staffed, given members and a committee, then emptied —
21.1s - ✅ Group membership and lifecycle › notes are recorded against the group and can be removed again —
8.9s - ✅ Group membership and lifecycle › an empty group is closed with a reason, and a group with members is refused —
19.9s
loan-account-actions.spec.ts
- ✅ Loan account lifecycle actions › new action menu items appear only for active loans —
7.1s - ✅ Loan account lifecycle actions › undo disbursal shows a confirm dialog and reverts the loan to Approved —
8.9s - ✅ Loan account lifecycle actions › write off requires confirmation and moves the loan out of Active status —
7.9s
loan-approved-amount-revision.spec.ts
- ✅ Loan approved and available-disbursement amount revision › revises the approved amount on an active, partially-disbursed loan —
7.2s - ✅ Loan approved and available-disbursement amount revision › revises the available disbursement amount, which also changes the approved amount —
6.8s - ✅ Loan approved and available-disbursement amount revision › shows the platform’s own refusal, not a generic validation message —
5.7s
loan-charge-off.spec.ts
- ✅ Loan servicing commands › charges a loan off through the UI and reverses it —
10.5s - ✅ Loan servicing commands › records a goodwill credit through the shared transaction form —
10.4s
loan-flows-non-admin-roles.spec.ts
- ✅ a loan taken through four separated duties, every account built in the UI › an administrator builds the four roles and accounts through the forms —
41.0s - ✅ a loan taken through four separated duties, every account built in the UI › the officer books the application and is not offered approval of their own loan —
15.3s - ✅ a loan taken through four separated duties, every account built in the UI › the approver, differing from the officer in one code, is offered it and approves —
5.9s - ✅ a loan taken through four separated duties, every account built in the UI › the approver is not offered disbursement of the loan they just approved —
5.0s - ✅ a loan taken through four separated duties, every account built in the UI › the disburser, holding only DISBURSE_LOAN, completes the payout end to end —
6.9s - ✅ a loan taken through four separated duties, every account built in the UI › and the same route now refuses an UPDATE_LOAN holder, as the platform does —
7.9s - ✅ a loan taken through four separated duties, every account built in the UI › a command with no mapped code is left to the platform rather than guessed at —
5.6s - ✅ a loan taken through four separated duties, every account built in the UI › a repayment duty reaches its own form, and records a repayment —
18.8s
loan-product-accounting.spec.ts
- ✅ Loan product accounting › a cash-accounting product is configured, round-trips on edit, and posts to the ledger —
33.8s
loan-repayment-gating.spec.ts
- ✅ Repayment is offered only where the platform allows it › withheld on a loan awaiting approval, offered once it is active —
10.0s - ✅ Repayment is offered only where the platform allows it › the platform refuses a repayment on a loan awaiting approval —
363ms
loan-servicing-rbac-backend.spec.ts
- ✅ loan servicing and charge command permissions, on the platform › each command is authorised by the code the UI declares for it —
5.2s - ✅ loan servicing and charge command permissions, on the platform › no portfolio write code authorises any of its entity commands —
5.6s - ✅ loan servicing and charge command permissions, on the platform › the deferred-income adjustments are not commands at loan level —
163ms - ✅ loan servicing and charge command permissions, on the platform › the deferred-income adjustments are commands on one transaction —
194ms
loan-servicing.spec.ts
- ✅ Loan servicing: notes and transaction adjustment › notes can be added and removed, with a confirm dialog on delete —
8.7s - ✅ Loan servicing: notes and transaction adjustment › a repayment transaction can be viewed and adjusted with a corrected amount —
10.5s
login.spec.ts
- ✅ Login › login page displays correctly —
1.4s - ✅ Login › login form has required fields —
1.4s - ✅ Login › submit button is disabled when form is empty —
1.3s - ✅ Login › submit button is enabled when form is filled —
1.7s
oidc-login-backend.spec.ts
- ✅ identity-provider route against a real Fineract › is offered beside the password form, and pressing it sends nothing to Fineract —
1.8s - ✅ identity-provider route against a real Fineract › a real password sign-in still gets a session with the route offered —
3.7s
parity-screens.spec.ts
- ✅ Screens added for platform parity › a manual journal entry can be read whole and reversed —
5.5s - ✅ Screens added for platform parity › an entry that is already reversed is not offered again —
4.9s - ✅ Screens added for platform parity › a report definition can be created, edited and deleted; a core one cannot —
5.5s - ✅ Screens added for platform parity › a core report opens read-only with only its in-use setting —
5.0s - ✅ Screens added for platform parity › a pending loan is approved from the queue, in a batch —
6.2s - ✅ Screens added for platform parity › a fixed deposit is listed as a deposit, not as a savings account —
5.4s - ✅ Screens added for platform parity › an office has a screen, and it carries its custom fields —
4.8s - ✅ Screens added for platform parity › a savings account carries notes, and the note survives a reload —
6.8s
rbac-backend-restricted-user.spec.ts
- ✅ a genuinely restricted Fineract user › holds exactly the permissions their role was granted —
161ms - ✅ a genuinely restricted Fineract user › reaches the screen their permission covers —
4.3s - ✅ a genuinely restricted Fineract user › is refused a screen their permission does not cover, by URL and by the backend —
4.7s - ✅ a genuinely restricted Fineract user › is refused a write screen they can read the list for, and the write itself —
6.9s - ✅ a genuinely restricted Fineract user › is not offered the actions it would be refused for —
4.4s - ✅ a genuinely restricted Fineract user › is shown an action it cannot take, disabled and saying what it needs —
6.1s - ✅ a genuinely restricted Fineract user › the superuser the rest of the suite uses is unaffected —
8.7s
rbac-dead-end-controls.spec.ts
- ✅ a client's account tables › withhold the link, but not the number, from a user who cannot open the account —
5.0s - ✅ a client's account tables › keep the link for a user who holds READ_LOAN —
5.2s - ✅ the office transactions list › withholds Create from a reader, and the route refuses it too —
5.8s - ✅ the office transactions list › offers Create to an administrator —
4.3s - ✅ the office transactions list › refuses the list at the door for the code the platform does not check —
4.6s - ✅ a group's member list › names the member without linking when the reader cannot open clients —
4.5s - ✅ a group's member list › keeps the link for a reader who holds READ_CLIENT —
5.3s
rbac-disabled-flag-backend.spec.ts
- ✅ rbacEnabled: false, with a user who holds only READ_CLIENT › is shown the whole navigation, including modules their role does not cover —
3.3s - ✅ rbacEnabled: false, with a user who holds only READ_CLIENT › is admitted by the route guard to a screen their role does not cover —
8.0s - ✅ rbacEnabled: false, with a user who holds only READ_CLIENT › is still refused by Fineract, which the flag does not reach —
613ms - ✅ rbacEnabled: false, with a user who holds only READ_CLIENT › is told the list is beyond their role, and not offered a retry that cannot succeed —
5.4s - ✅ rbacEnabled: false, with a user who holds only READ_CLIENT › gets the same treatment on a second refused list, not a one-screen fix —
5.9s - ✅ rbacEnabled: false, with a user who holds only READ_CLIENT › still loads the list their role does cover, with no failure state at all —
5.3s
rbac-multi-permission.spec.ts
- ✅ a route declaring more than one permission code (OR semantics) › is admitted by either declared code alone —
5.1s - ✅ a route declaring more than one permission code (OR semantics) › is admitted by the other declared code alone —
4.9s - ✅ a route declaring more than one permission code (OR semantics) › is refused when holding neither declared code, by the router and by the backend —
5.4s - ✅ ALL_FUNCTIONS_READ, against the real Fineract permission catalogue › reaches read screens across modules it holds no specific code for —
9.4s - ✅ ALL_FUNCTIONS_READ, against the real Fineract permission catalogue › is refused every write screen, and the writes themselves —
6.9s - ✅ a restricted session across a real page reload › keeps the same permission boundary after reloading, not just after a fresh login —
10.0s - ✅ a second real action-level gate, distinct from loan repayment › is shown the Approve action disabled and naming what it needs, refused by the backend too —
5.6s - ✅ Security module writes (users, roles), against the real backend › reaches the list screens but is refused the write screens —
11.2s - ✅ Security module writes (users, roles), against the real backend › is refused creating a user and modifying a role, by the backend itself —
249ms
rbac-office-scoped-user.spec.ts
- ✅ an account scoped to a branch office › differs from a head-office account in office alone, not in permissions —
401ms - ✅ an account scoped to a branch office › is shown only its own office in the client list, and told which office it is in —
4.7s - ✅ an account scoped to a branch office › sees a shorter list than an identically-permissioned head-office account —
4.5s - ✅ an account scoped to a branch office › is refused another office’s client by the platform, and told so once —
5.1s - ✅ an account scoped to a branch office › can still open the record that is in its own office —
4.9s - ✅ an account scoped to a branch office › is not offered an edit it would be refused for on the Offices screen —
7.0s
bulk-import-entity-types.spec.ts
- ✅ every offered entity type is one the platform accepts —
10.2s
delinquency-configuration.spec.ts
- ✅ creates a delinquency range, then edits it —
8.5s - ✅ creates a delinquency bucket from the ranges the template offers —
8.6s
full-demo.spec.ts
- ✅ Full feature demo recording › walk through loan schedule type, lifecycle, custom fields, collateral, and disbursement —
58.4s
interest-rate-charts.spec.ts
- ✅ lists charts without a server error —
7.3s
loan-lifecycle.spec.ts
- ✅ Loan lifecycle: creation, approval, disbursement › create, approve, and disburse a Cumulative loan —
30.9s - ✅ Loan lifecycle: creation, approval, disbursement › create, approve, and disburse a Progressive loan —
30.8s - ✅ Loan lifecycle: creation, approval, disbursement › an approved loan can be returned to pending approval —
28.5s - ✅ Loan lifecycle: creation, approval, disbursement › the delinquency tab reads a real loan, and the empty data tabs stay hidden —
27.9s
loan-schedule-type.spec.ts
- ✅ Loan Schedule Type (Cumulative vs Progressive) › loan products list shows a schedule type chip per product —
9.2s - ✅ Loan Schedule Type (Cumulative vs Progressive) › create a Progressive loan product end-to-end and verify it round-trips —
14.3s - ✅ Loan Schedule Type (Cumulative vs Progressive) › loan creation shows the schedule type badge for a Progressive product —
10.1s
rbac-permissionless-user.spec.ts
- ✅ an account whose role grants nothing › is authenticated by Fineract and authorized by it for nothing —
713ms - ✅ an account whose role grants nothing › is signed in and landed on a screen rather than stranded at the login form —
4.8s - ✅ an account whose role grants nothing › is offered no permission-gated module, and refused each one by URL and by Fineract —
10.5s - ✅ an account whose role grants nothing › is left a navigation whose entries actually work —
5.2s
report-mailing-job.spec.ts
- ✅ report mailing job creation › is no longer refused for a missing parameter —
5.8s - ✅ report mailing job creation › the payload carries the locale and the schedule —
5.2s - ✅ report mailing job creation at the narrow viewport › every field is reachable and the job still saves at 320px —
8.1s
report-parameter-backend.spec.ts
- ✅ Dynamic report parameters against Fineract › keeps the parameter form available when a report has cascading lookups —
4.4s - ✅ Dynamic report parameters against Fineract › changing Office changes the Client Listing row set —
6.5s - ✅ Cascading report parameters against Fineract › sends the parent value to the child lookup and clears the child when it changes —
6.9s - ✅ Chart reports against Fineract › renders a chart report as a chart rather than a table —
5.0s
savings-flows-non-admin-roles.spec.ts
- ✅ a savings account taken through four separated duties, every account built in the UI › an administrator builds the five roles and accounts through the forms —
47.0s - ✅ a savings account taken through four separated duties, every account built in the UI › the officer books the application and is not offered approval of it —
8.3s - ✅ a savings account taken through four separated duties, every account built in the UI › the approver, differing in one code, is offered it and approves —
5.0s - ✅ a savings account taken through four separated duties, every account built in the UI › the activator activates, and cannot take a deposit —
5.4s - ✅ a savings account taken through four separated duties, every account built in the UI › the teller, holding only the transaction codes, records a deposit end to end —
5.3s - ✅ a savings account taken through four separated duties, every account built in the UI › the cash-withdrawal control asks for the cash code, not the application one —
18.3s - ✅ a savings account taken through four separated duties, every account built in the UI › the transaction route refuses an UPDATE_SAVINGSACCOUNT holder, as the platform does —
6.5s
savings-transaction-correction.spec.ts
- ✅ Savings transaction correction › a deposit is reversed and a hold is released —
6.1s
savings-transaction-gating.spec.ts
- ✅ Savings transactions are offered only on an active account › withheld while awaiting approval, offered once active —
5.6s - ✅ Savings transactions are offered only on an active account › the platform refuses a deposit on an account awaiting approval —
224ms
share-account-servicing.spec.ts
- ✅ Share account servicing › an account is approved, activated, traded and closed —
12.9s - ✅ Share account servicing › an application can be rejected —
6.0s
share-product-accounting.spec.ts
- ✅ Share product accounting › a share product is mapped to equity and round-trips on edit —
25.1s
teller-cash-management.spec.ts
- ✅ Teller cash management › a cashier is listed, receives an allocation, and settles cash back —
25.1s - ✅ Teller cash management › settling more than the cashier holds is refused and the form stays usable —
11.7s
tenant-date-defaults-backend.spec.ts
- ✅ Date defaults come from the platform, not the browser › a client can be created while the browser is a day ahead —
6.0s
working-capital-loan-actions-backend.spec.ts
- ✅ Working Capital loan actions, against a real Fineract › charges off an active loan, and then offers only the undo —
8.1s - ✅ Working Capital loan actions, against a real Fineract › credits the borrower with a goodwill credit, and the balance falls —
6.4s - ✅ Working Capital loan actions, against a real Fineract › writes the loan off, which closes it and opens the recovery path —
7.5s
working-capital-product-accounting-backend.spec.ts
- ✅ Working Capital product accounting, on the platform › configures the accrual rule and its twelve accounts, and the platform accepts it —
15.7s - ✅ Working Capital product accounting, on the platform › prefills a configured product and re-saves it without blanking anything —
5.2s
working-capital-rbac-backend.spec.ts
- ✅ Working Capital command permissions, on the platform › each command is authorised by the code the UI declares for it —
5.4s - ✅ Working Capital command permissions, on the platform › UPDATE_WORKINGCAPITALLOAN authorises none of them —
2.2s
working-capital-servicing-backend.spec.ts
- ✅ transaction corrections › undoes a repayment, and the platform marks it reversed —
6.5s - ✅ transaction corrections › offers no correction on the disbursement, which the platform refuses by type —
4.6s - ✅ transaction corrections › adjusts a repayment, which reverses it and books the new amount —
5.3s - ✅ charge adjustment › adjusts a charge, and the outstanding amount follows —
5.0s - ✅ custom datatables › adds, edits and deletes a row of a multi-row table on the loan —
7.0s - ✅ custom datatables › creates a loan that a create-time check would otherwise refuse —
7.0s
Slowest 10 — what the shard counts should be tuned against
| Test | Spec | Time |
|---|---|---|
| Full feature demo recording › walk through loan schedule type, lifecycle, custom fields, collateral, and disbursement | full-demo.spec.ts |
58.4s |
| a savings account taken through four separated duties, every account built in the UI › an administrator builds the five roles and accounts through the forms | savings-flows-non-admin-roles.spec.ts |
47.0s |
| a loan taken through four separated duties, every account built in the UI › an administrator builds the four roles and accounts through the forms | loan-flows-non-admin-roles.spec.ts |
41.0s |
| Loan product accounting › a cash-accounting product is configured, round-trips on edit, and posts to the ledger | loan-product-accounting.spec.ts |
33.8s |
| Loan lifecycle: creation, approval, disbursement › create, approve, and disburse a Cumulative loan | loan-lifecycle.spec.ts |
30.9s |
| Loan lifecycle: creation, approval, disbursement › create, approve, and disburse a Progressive loan | loan-lifecycle.spec.ts |
30.8s |
| Loan lifecycle: creation, approval, disbursement › an approved loan can be returned to pending approval | loan-lifecycle.spec.ts |
28.5s |
| Loan lifecycle: creation, approval, disbursement › the delinquency tab reads a real loan, and the empty data tabs stay hidden | loan-lifecycle.spec.ts |
27.9s |
| Teller cash management › a cashier is listed, receives an allocation, and settles cash back | teller-cash-management.spec.ts |
25.1s |
| Share product accounting › a share product is mapped to equity and round-trips on edit | share-product-accounting.spec.ts |
25.1s |
📼 Download the HTML report, videos and traces — see the playwright-report-backend artifact.
Generated by run 39260395538 from 41201a0. The run executed a fork branch, so treat its contents as unverified.
🔄 What this change talks toFrom the 10 application TypeScript file(s) changed in this pull request. sequenceDiagram
autonumber
participant FineractRoleApi as FineractRoleApi (Service)
participant RolesService as RolesService (API client)
participant Fineract as Fineract (REST API)
participant FineractUserApi as FineractUserApi (Service)
participant UsersService as UsersService (API client)
participant PasswordPreferencesService as PasswordPreferencesService (API client)
participant UserProfileComponent as UserProfileComponent (Component)
participant AuthService as AuthService (Injected)
participant USER_API as USER_API (Injected)
participant NotificationService as NotificationService (Injected)
participant I18N as I18N (Injected)
participant RolesListComponent as RolesListComponent (Component)
FineractRoleApi->>RolesService: getRoles()
RolesService->>Fineract: getRoles()
Fineract-->>RolesService: response
FineractRoleApi->>RolesService: getRolesRoleId()
RolesService->>Fineract: getRolesRoleId()
Fineract-->>RolesService: response
FineractRoleApi->>RolesService: getRolesRoleIdPermissions()
RolesService->>Fineract: getRolesRoleIdPermissions()
Fineract-->>RolesService: response
FineractRoleApi->>RolesService: postRoles()
RolesService->>Fineract: postRoles()
Fineract-->>RolesService: response
FineractRoleApi->>RolesService: putRolesRoleId()
RolesService->>Fineract: putRolesRoleId()
Fineract-->>RolesService: response
FineractRoleApi->>RolesService: putRolesRoleIdPermissions()
RolesService->>Fineract: putRolesRoleIdPermissions()
Fineract-->>RolesService: response
FineractRoleApi->>RolesService: postRolesRoleId()
RolesService->>Fineract: postRolesRoleId()
Fineract-->>RolesService: response
FineractRoleApi->>RolesService: deleteRolesRoleId()
RolesService->>Fineract: deleteRolesRoleId()
Fineract-->>RolesService: response
FineractUserApi->>UsersService: getUsers()
UsersService->>Fineract: getUsers()
Fineract-->>UsersService: response
FineractUserApi->>UsersService: getUsersUserId()
UsersService->>Fineract: getUsersUserId()
Fineract-->>UsersService: response
FineractUserApi->>UsersService: getUsersTemplate()
UsersService->>Fineract: getUsersTemplate()
Fineract-->>UsersService: response
FineractUserApi->>UsersService: postUsers()
UsersService->>Fineract: postUsers()
Fineract-->>UsersService: response
FineractUserApi->>UsersService: putUsersUserId()
UsersService->>Fineract: putUsersUserId()
Fineract-->>UsersService: response
FineractUserApi->>PasswordPreferencesService: getPasswordpreferences()
PasswordPreferencesService->>Fineract: getPasswordpreferences()
Fineract-->>PasswordPreferencesService: response
FineractUserApi->>UsersService: postUsersUserIdPwd()
UsersService->>Fineract: postUsersUserIdPwd()
Fineract-->>UsersService: response
Truncated for readability: 5 further participant(s) and 10 further call(s) not shown.
Generated by run 39260395579 from |
`main` has been red on Translation Check since apache#819 merged, and neither apache#819 nor the change it landed beside was wrong on its own — both were green. apache#816 renamed `ON_HOLD_TRANSACTIONS.REASON` to `LOAN`, because the column shows the loan the funds are held against and never held a reason. apache#819 was authored before that and translated the key as it then stood. Git merged the two cleanly: they touch different keys in different files, so there was nothing to conflict. The result is a catalogue that disagrees with `en.json` in both directions at once: fr.json defines ON_HOLD_TRANSACTIONS.REASON, which en.json does not — remove it. fr.json covers 2746 keys, down from 2747. So `Motif` becomes `Prêt` — a retranslation rather than a rename, since the column means something else now. The same merge left the sixteen keys from apache#817's password and access work untranslated, for the same reason: that branch predated `fr.json` existing, so there was no French file for it to add them to. They are translated here, which takes `fr` to 2763/2763, and the coverage baseline is refreshed so the ratchet holds the gain rather than the old floor. `app.language.fr` is added to `hi.json` too. apache#819 registered French in the language pickers and gave `en` and `fr` a label for it, so a Hindi user was offered the new language under a raw translation key.
Unit Tests and two of the three real-Fineract E2E shards have been failing on `main`, and
not because of anything that merged. They fail on the clock. Every run started before
17:45 UTC is green; both runs started after 18:26 UTC are red, with the same four jobs:
19:07Z apache#817 merge 4 failures
18:26Z apache#819 merge 4 failures
15:02Z all green
14:23Z apache#816 merge all green
12:46Z all green
`Asia/Kolkata` is UTC+5:30, so its date turns over at 18:30 UTC. `AppConfig.tenantTimezone`
defaults to it, which is the zone the dialog resolves today in — deliberately, because the
platform validates `activate` against a date it stamped in the tenant's zone, not the
browser's. Two cases here asserted `component.actionDate()` against
`toIsoDate(new Date())`, the browser's today. For five and a half hours each evening those
are different days, the component answers the tenant's and the assertion wants the
browser's, and the job goes red:
AssertionError: expected '2026-10-20' to be '2026-10-21'
The spec's own header claimed these cases "hold whatever time it is". They now do, by
pinning the platform's clock through `recordServerTime` instead of reading the real one.
It is pinned inside the window that was breaking them — 20:00Z on the 15th, already the
16th in `Asia/Kolkata` — so the cases assert the tenant's date against a browser that
would say the 15th, which is the disagreement they exist for. Verified green at
`Asia/Kolkata`, `UTC`, `America/New_York`, `Etc/GMT-10`, `Etc/GMT+12` and
`Pacific/Kiritimati`, and failing 4 of 5 when the component is reverted to the browser
clock — before this it caught that regression only between 18:30 and midnight UTC.
The relative dates move with it: `offsetFromToday` stepped the browser's clock with local
getters, so the floor cases drifted on the same boundary. They are ISO arithmetic from the
pinned date now, anchored at midday UTC so no offset can shift them.
The two E2E shards are the same disagreement — shard 1 holds `group-membership.spec.ts`
and shard 3 `tenant-date-defaults-backend.spec.ts`, both written for this window — but
they are not addressed here. Reproducing them needs a live platform inside the window, and
a guess at an E2E spec is not worth making.
Three issues on the Security screens, all measured against a running Fineract: #752, #753 and #755.
1. A rejected password was printed in the toast (#752)
Fineract returns the value it rejected in the error's
args, and the toast formatter appends them. For a password that is a disclosure — measured,POST /userswith a password the policy refuses answers:{ "parameterName": "password", "userMessageGlobalisationCode": "validation.msg.user.password.does.not.match.regexp", "args": [ { "value": "" }, { "value": "^(?!.*(.)\\1)…" } ] }and the
repeatPasswordentry beside it carries both values. The field is masked precisely so the value is not readable on screen, over a shoulder, in a screenshot or in a support ticket.Args are now dropped for any parameter whose name contains
password,secret,pin,token,credentialorotp— a substring test, sorepeatPasswordandclientSecretare covered without enumerating spellings. Dropped entirely rather than filtered for the value: the only other thing in them is the policy regex, which no operator can act on. The rules still reach the user, and a non-sensitive parameter keeps its args — a refused amount or date is what that detail is for.2. The password is now checked before it is sent (#753)
Create User validated both password fields for presence only: with different values in them Save stayed enabled and the only feedback was the 400 above.
The rules are the tenant's own.
GET /passwordpreferencesanswers the active policy and/passwordpreferences/templateall three (simple,secure,strong), each with the platform's own description — shown as the field's helper text rather than restated. What that endpoint does not answer is the regex it enforces: that appears only in theargsof a rejected password, which carry the password with it, socore/security/password-policy.tsmirrors the rules bykey. The fallback honours the one obligation that creates — an unrecognised key gets the loosest rules, so a policy this code has not heard of cannot refuse a password the platform would have accepted.The form names the rules a password still fails (
repeat no character twice in a rowfor one refused on itsssalone, which the platform's message never says), reports a mismatch under the confirmation field, and holds Save until both are right. The profile screen's change-password form shares the same rules through the same adapter operation, so the two cannot drift.The password pair also moved into signals:
[(ngModel)]="user().password"mutates the object a signal holds without notifying it, so nothing derived from it would recompute — the trap that emptied the floating-rate editor in #815.3. Access can be withdrawn (#755)
Roles and Users offered create and edit only, so cutting a leaver's access meant editing their roles down to nothing, and a role created by mistake stayed in every picker.
GET /roleshas always carrieddisabled— the stockSelf Service Userrole answerstrue— and the list now shows it as a status and acts on it.GET /users/templatealready filters disabled roles out of the options for a new user, so switching one off is enough to stop it being assigned.All four actions are gated on
DISABLE_ROLE,ENABLE_ROLE,DELETE_ROLEandDELETE_USER, and confirmed first (destructively for the deletes).DELETE /roles/{id}while a user holds the role answers 403error.msg.role.associated.with.users.deleted, and that reaches the user as "Role with identifier 34 associated with users" —errorInterceptoralready reports a domain-rule 403 in the platform's own words.Checks
list-pagination.spec.ts's profile change-password test mockedGET /passwordpreferencesaskey: 'strong'but described it as "At least eight characters with mixed case and a number", and typed an eight-character password — a fixture that contradicted itself, and that the new gate correctly refused. It now carries the platform's ownstrongdescription and a password that satisfies it, and on the way through it asserts what the gate is for: the short password names the rule it breaks and Save stays disabled, so nothing reaches/users/1/pwduntil the password would be accepted.passsword1A!xreports This password must still: repeat no character twice in a row and nothing else; a mismatch shows under the confirmation and Save is disabled; the probe role went ACTIVE → DISABLED → ACTIVE and was then deleted; a probe user was deleted; the signed-in account offers no Delete; and deleting an assigned role was refused with the platform's own reason.lint:pruneleaveseslint-suppressions.jsonunchanged),api:surfacere-recorded for the three new operations,tsconfig.app.jsonandtsconfig.e2e.jsonboth type-clean,i18n:check,format:check,check:permission-codes,check:template-text,check:a11y-names,check:ui-primitives,check:icons,check:route-permissions,check:responsive,check:nav-ids,check:internal-endpointsall clean.Note on #492
Closed by earlier work, not by this PR:
/profilealready has a Change Password form. What it was missing is the rule-level validation above, which it now shares with Create User.🤖 Generated with Claude Code