feat(users): auto-enroll admin-chosen OTP channel as MFA factor at creation
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Admin now picks a mandatory OTP delivery method (Email/Mobile) when creating a user. The channel is enrolled immediately as a verified m_user_mfa_factor row, so the existing 2FA system (MfaFactorBackedTwoFactorServiceImpl + /twofactor) challenges the user for OTP on their very first login - reusing the already-built login-OTP infrastructure instead of a parallel flow. Also adds a versioned, admin-manageable user-agreement (disclaimer) API (/v1/useragreements) with per-user acceptance tracking, checked right after every login, plus a Liquibase cleanup migration and a shared OTP-destination masking utility extracted from the two existing login-OTP services. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -435,6 +435,11 @@
|
||||
<include file="parts/0412_activate_outward_clearing_batch_job.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0413_inward_item_verify_signature_permission.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0414_inward_item_full_source_details.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0415_add_otp_delivery_method_and_first_login_gate_to_appuser.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0416_create_user_agreement_tables.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0417_add_first_login_gate_config.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0418_add_user_agreement_permissions.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0419_revert_first_login_gate_column_and_config.xml" relativeToChangelogFile="true"/>
|
||||
<!-- NOTE: the numbers below (0341-0359) re-use prefixes already used above by the chequebook/CTS
|
||||
clearing catchup migrations — these come from a long-diverged branch (uate) that assigned its own
|
||||
sequence independently. Liquibase tracks changesets by filename + id + author, not by the numeric
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
|
||||
<changeSet author="fineract" id="1">
|
||||
<addColumn tableName="m_appuser">
|
||||
<column name="otp_delivery_method" type="varchar(10)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="first_login_gate_pending" type="boolean" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<!--
|
||||
Versioned admin-manageable "user agreement" text (bank policy/warning) that newly-created users
|
||||
must accept during the first-login gate. Only one row may be active at a time - enforced in the
|
||||
write service (UserAgreementWritePlatformServiceImpl), not a DB constraint, since flipping "active"
|
||||
is a two-row transaction (deactivate old, activate new).
|
||||
-->
|
||||
<changeSet id="0416_create_m_user_agreement" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><tableExists tableName="m_user_agreement"/></not></preConditions>
|
||||
<createTable tableName="m_user_agreement">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="version" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="title" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="body" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="is_active" type="boolean" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="created_by" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="created_on" type="datetime" defaultValueComputed="CURRENT_TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<addUniqueConstraint tableName="m_user_agreement" columnNames="version"
|
||||
constraintName="uq_user_agreement_version"/>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_user_agreement" baseColumnNames="created_by"
|
||||
constraintName="fk_user_agreement_created_by"
|
||||
referencedTableName="m_appuser" referencedColumnNames="id"/>
|
||||
</changeSet>
|
||||
|
||||
<!--
|
||||
Audit-proof record of a user accepting a given version of the agreement during the first-login gate.
|
||||
agreement_version is a denormalized snapshot of m_user_agreement.version at acceptance time, so the
|
||||
acceptance record survives future version bumps unaffected.
|
||||
-->
|
||||
<changeSet id="0416_create_m_user_agreement_acceptance" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><tableExists tableName="m_user_agreement_acceptance"/></not></preConditions>
|
||||
<createTable tableName="m_user_agreement_acceptance">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="appuser_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agreement_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agreement_version" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="accepted_on" type="datetime" defaultValueComputed="CURRENT_TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ip_address" type="VARCHAR(45)"/>
|
||||
<column name="user_agent" type="VARCHAR(500)"/>
|
||||
</createTable>
|
||||
|
||||
<addUniqueConstraint tableName="m_user_agreement_acceptance" columnNames="appuser_id, agreement_id"
|
||||
constraintName="uq_user_agreement_acceptance"/>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_user_agreement_acceptance" baseColumnNames="appuser_id"
|
||||
constraintName="fk_user_agreement_acceptance_user"
|
||||
referencedTableName="m_appuser" referencedColumnNames="id"/>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_user_agreement_acceptance" baseColumnNames="agreement_id"
|
||||
constraintName="fk_user_agreement_acceptance_agreement"
|
||||
referencedTableName="m_user_agreement" referencedColumnNames="id"/>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
|
||||
<changeSet author="fineract" id="1">
|
||||
<insert tableName="c_configuration">
|
||||
<column name="name" value="enable-first-login-otp-and-agreement-verification"/>
|
||||
<column name="value" valueNumeric="0"/>
|
||||
<column name="enabled" valueBoolean="false"/>
|
||||
<column name="is_trap_door" valueBoolean="false"/>
|
||||
<column name="description" value="If enabled, newly created users must verify OTP via their chosen channel and accept the active user agreement on first login before access is granted. Verification also auto-enrolls that channel as ongoing MFA. Existing users are unaffected. Value unused."/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<!--
|
||||
Permissions for the admin "user agreement" management API (/v1/useragreements), used to
|
||||
publish the versioned agreement text every user must accept right after logging in.
|
||||
-->
|
||||
|
||||
<changeSet id="0418-user-agreement-create-permission" author="CBS" runOnChange="false">
|
||||
<sql>
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'organisation', 'CREATE_USERAGREEMENT', 'USERAGREEMENT', 'CREATE', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'CREATE_USERAGREEMENT');
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0418-user-agreement-read-permission" author="CBS" runOnChange="false">
|
||||
<sql>
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'organisation', 'READ_USERAGREEMENT', 'USERAGREEMENT', 'READ', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'READ_USERAGREEMENT');
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<!--
|
||||
Reverts 0415/0417: the first-login "gate" (its own status/complete API, pending-flag, and
|
||||
dedicated toggle) was replaced by simply auto-enrolling the admin-chosen OTP channel as an
|
||||
already-verified m_user_mfa_factor row at user-creation time - the existing 2FA system
|
||||
(MfaFactorBackedTwoFactorServiceImpl + /twofactor + TwoFactorAuthenticationComponent) then
|
||||
challenges the user for OTP on their very first login with no new code at all. Neither
|
||||
column nor the config toggle are read by any code anymore.
|
||||
-->
|
||||
<changeSet id="0419-drop-otp-delivery-method-column" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<columnExists tableName="m_appuser" columnName="otp_delivery_method"/>
|
||||
</preConditions>
|
||||
<dropColumn tableName="m_appuser" columnName="otp_delivery_method"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0419-drop-first-login-gate-pending-column" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<columnExists tableName="m_appuser" columnName="first_login_gate_pending"/>
|
||||
</preConditions>
|
||||
<dropColumn tableName="m_appuser" columnName="first_login_gate_pending"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0419-remove-first-login-gate-config" author="CBS">
|
||||
<sql>
|
||||
DELETE FROM c_configuration WHERE name = 'enable-first-login-otp-and-agreement-verification';
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user