merege code feature/Backend-DSC-MFA branch
This commit is contained in:
@@ -664,8 +664,6 @@ fineract.openproject.api-token=${FINERACT_OPENPROJECT_API_TOKEN:57dd9f933f113c72
|
||||
|
||||
# ── DIP / MNRL Mobile Number Lookup ──────────────────────────────────────────
|
||||
fineract.mobile-lookup.base-url=${FINERACT_MOBILE_LOOKUP_BASE_URL:http://localhost:8080}
|
||||
|
||||
|
||||
# ── Login OTP (2FA) — uses LincPay credentials above ─────────────────────────
|
||||
fineract.security.otp.lincpay.generate-path=${FINERACT_OTP_LINCPAY_GENERATE_PATH:/mobile/generate-otp}
|
||||
fineract.security.otp.lincpay.verify-path=${FINERACT_OTP_LINCPAY_VERIFY_PATH:/verify-mobile-otp}
|
||||
@@ -686,3 +684,4 @@ spring.mail.properties.mail.transport.protocol=smtp
|
||||
fineract.email.from-address=${FINERACT_EMAIL_FROM_ADDRESS:no-reply@cbsnexora.com}
|
||||
fineract.email.from-name=${FINERACT_EMAIL_FROM_NAME:CBSnexora}
|
||||
fineract.email.support-address=${FINERACT_EMAIL_SUPPORT_ADDRESS:support@cbsnexora.com}
|
||||
|
||||
|
||||
@@ -308,4 +308,8 @@
|
||||
<include file="parts/0289_seed_read_otp_log_permission.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0290_create_otp_log_auditor_role.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0291_grant_read_otp_log_to_bankadmin_roles.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0292_create_m_bank_user_dsc.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0293_create_m_dsc_challenge.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0294_add_tfa_token_to_dsc_challenge.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0295_unique_email_mobile_on_m_appuser.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
<?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
|
||||
-->
|
||||
<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">
|
||||
|
||||
<!--
|
||||
Stores Digital Signature Certificate (DSC) mappings per AppUser. A user may have multiple
|
||||
rows; queries filter on (is_active = true, is_deleted = false, cert_status = 'ACTIVE') to
|
||||
find usable mappings.
|
||||
|
||||
cert serial is globally unique — one cert in the world has one serial — so it's the natural
|
||||
lookup key at login-verify time.
|
||||
-->
|
||||
<changeSet id="0292_create_m_bank_user_dsc" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><tableExists tableName="m_bank_user_dsc"/></not></preConditions>
|
||||
<createTable tableName="m_bank_user_dsc">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="appuser_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<!-- Parity with external 'bank_user_dsc' reference schema -->
|
||||
<column name="dsc_user_id" type="VARCHAR(64)"/>
|
||||
<column name="dsc_serial_no" type="VARCHAR(128)">
|
||||
<constraints nullable="false" unique="true" uniqueConstraintName="uq_bank_user_dsc_serial"/>
|
||||
</column>
|
||||
<column name="dsc_name" type="VARCHAR(255)"/>
|
||||
<column name="dsc_public_key" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="dsc_expiry_date" type="DATE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="date_of_submission" type="datetime" defaultValueComputed="CURRENT_TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="dsc_type" type="VARCHAR(32)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="common_name" type="VARCHAR(255)"/>
|
||||
|
||||
<!-- Cert metadata for display + future chain validation -->
|
||||
<column name="issued_to" type="VARCHAR(512)"/>
|
||||
<column name="issued_by" type="VARCHAR(512)"/>
|
||||
|
||||
<!-- Lifecycle: ACTIVE / REVOKED / EXPIRED -->
|
||||
<column name="cert_status" type="VARCHAR(16)" defaultValue="ACTIVE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="revoked_on" type="datetime"/>
|
||||
<column name="revoked_by" type="BIGINT"/>
|
||||
<column name="revoke_reason" type="VARCHAR(255)"/>
|
||||
|
||||
<!-- Standard Fineract soft-delete + audit columns -->
|
||||
<column name="is_active" type="boolean" defaultValueBoolean="true">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="is_deleted" type="boolean" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="created_by" type="BIGINT"/>
|
||||
<column name="created_at" type="datetime" defaultValueComputed="CURRENT_TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="modified_by" type="BIGINT"/>
|
||||
<column name="modified_on" type="datetime"/>
|
||||
<column name="deleted_by" type="BIGINT"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
<column name="version" type="INT" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_bank_user_dsc" baseColumnNames="appuser_id"
|
||||
constraintName="fk_bank_user_dsc_user"
|
||||
referencedTableName="m_appuser" referencedColumnNames="id"/>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_bank_user_dsc" baseColumnNames="revoked_by"
|
||||
constraintName="fk_bank_user_dsc_revoked_by"
|
||||
referencedTableName="m_appuser" referencedColumnNames="id"/>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_bank_user_dsc" baseColumnNames="created_by"
|
||||
constraintName="fk_bank_user_dsc_created_by"
|
||||
referencedTableName="m_appuser" referencedColumnNames="id"/>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_bank_user_dsc" baseColumnNames="modified_by"
|
||||
constraintName="fk_bank_user_dsc_modified_by"
|
||||
referencedTableName="m_appuser" referencedColumnNames="id"/>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_bank_user_dsc" baseColumnNames="deleted_by"
|
||||
constraintName="fk_bank_user_dsc_deleted_by"
|
||||
referencedTableName="m_appuser" referencedColumnNames="id"/>
|
||||
|
||||
<!-- Common query: list active DSCs for a user -->
|
||||
<createIndex tableName="m_bank_user_dsc" indexName="idx_bank_user_dsc_user_status">
|
||||
<column name="appuser_id"/>
|
||||
<column name="cert_status"/>
|
||||
<column name="is_deleted"/>
|
||||
</createIndex>
|
||||
|
||||
<!-- Scheduled job: flip ACTIVE → EXPIRED when notAfter passes -->
|
||||
<createIndex tableName="m_bank_user_dsc" indexName="idx_bank_user_dsc_expiry">
|
||||
<column name="dsc_expiry_date"/>
|
||||
<column name="cert_status"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
<!-- Permissions: read / create / delete (delete = soft revoke) -->
|
||||
<changeSet id="0292_seed_dsc_permissions" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'READ_DSC'</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="special"/>
|
||||
<column name="code" value="READ_DSC"/>
|
||||
<column name="entity_name" value="DSC"/>
|
||||
<column name="action_name" value="READ"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="special"/>
|
||||
<column name="code" value="CREATE_DSC"/>
|
||||
<column name="entity_name" value="DSC"/>
|
||||
<column name="action_name" value="CREATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="special"/>
|
||||
<column name="code" value="DELETE_DSC"/>
|
||||
<column name="entity_name" value="DSC"/>
|
||||
<column name="action_name" value="DELETE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<!-- Self Service User gets the trio so end-users can enroll/revoke their own DSC. -->
|
||||
<changeSet id="0292_grant_dsc_permissions_to_self_service_role" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="1">SELECT COUNT(*) FROM m_role WHERE name = 'Self Service User'</sqlCheck>
|
||||
</preConditions>
|
||||
<sql>
|
||||
INSERT INTO m_role_permission (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM m_role r, m_permission p
|
||||
WHERE r.name = 'Self Service User'
|
||||
AND p.code IN ('READ_DSC', 'CREATE_DSC', 'DELETE_DSC')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_role_permission rp
|
||||
WHERE rp.role_id = r.id AND rp.permission_id = p.id
|
||||
)
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
<?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
|
||||
-->
|
||||
<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">
|
||||
|
||||
<!--
|
||||
One-shot nonce for a DSC enroll or login-2FA cycle. Rows are short-lived (~2 min TTL) and
|
||||
transition PENDING → VERIFIED / FAILED / EXPIRED exactly once. Kept after terminal state for
|
||||
audit; cleanup is the job of a scheduled task.
|
||||
|
||||
random_no is the public handle embedded in the JNLP and in the helper's callback URL —
|
||||
UUID v4 length, sufficient entropy that guessing a pending nonce is infeasible.
|
||||
-->
|
||||
<changeSet id="0293_create_m_dsc_challenge" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><tableExists tableName="m_dsc_challenge"/></not></preConditions>
|
||||
<createTable tableName="m_dsc_challenge">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="random_no" type="VARCHAR(64)">
|
||||
<constraints nullable="false" unique="true" uniqueConstraintName="uq_dsc_challenge_random_no"/>
|
||||
</column>
|
||||
<column name="appuser_id" type="BIGINT"/>
|
||||
<column name="purpose" type="VARCHAR(32)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="challenge" type="VARCHAR(128)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="status" type="VARCHAR(16)" defaultValue="PENDING">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="cert_serial_returned" type="VARCHAR(128)"/>
|
||||
<column name="signature_b64" type="TEXT"/>
|
||||
<column name="dsc_id" type="BIGINT"/>
|
||||
<column name="error_message" type="VARCHAR(512)"/>
|
||||
<column name="attempts" type="INT" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="expires_at" type="datetime">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="verified_at" type="datetime"/>
|
||||
<column name="created_at" type="datetime" defaultValueComputed="CURRENT_TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ip_address" type="VARCHAR(64)"/>
|
||||
<column name="user_agent" type="VARCHAR(512)"/>
|
||||
</createTable>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_dsc_challenge" baseColumnNames="appuser_id"
|
||||
constraintName="fk_dsc_challenge_user"
|
||||
referencedTableName="m_appuser" referencedColumnNames="id"/>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_dsc_challenge" baseColumnNames="dsc_id"
|
||||
constraintName="fk_dsc_challenge_dsc"
|
||||
referencedTableName="m_bank_user_dsc" referencedColumnNames="id"/>
|
||||
|
||||
<!-- "Open challenges for this user" — drives the status-poll endpoint -->
|
||||
<createIndex tableName="m_dsc_challenge" indexName="idx_dsc_challenge_user_status">
|
||||
<column name="appuser_id"/>
|
||||
<column name="status"/>
|
||||
</createIndex>
|
||||
|
||||
<!-- Cleanup job filter -->
|
||||
<createIndex tableName="m_dsc_challenge" indexName="idx_dsc_challenge_expires_at">
|
||||
<column name="expires_at"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?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
|
||||
-->
|
||||
<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">
|
||||
|
||||
<!--
|
||||
DSC login flow stores the minted TFA access token on the challenge row instead of returning
|
||||
it to the helper. The browser then calls POST /v1/login/dsc/{rn}/claim (basic-auth required)
|
||||
to retrieve the token exactly once.
|
||||
|
||||
This stops the helper from carrying a bearer credential back into a browser process and
|
||||
keeps the token's emission scoped to a fresh authenticated request.
|
||||
-->
|
||||
<changeSet id="0294_add_tfa_token_to_dsc_challenge" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_dsc_challenge"/>
|
||||
<not><columnExists tableName="m_dsc_challenge" columnName="tfa_token"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_dsc_challenge">
|
||||
<column name="tfa_token" type="VARCHAR(255)"/>
|
||||
<column name="tfa_token_claimed_at" type="datetime"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?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
|
||||
-->
|
||||
<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">
|
||||
|
||||
<!--
|
||||
Enforce uniqueness on m_appuser.email and m_appuser.mobile_no.
|
||||
|
||||
Username is already unique (constraint name 'username_org' from the upstream schema).
|
||||
Email is NOT NULL in the entity; mobile_no IS nullable. Both PostgreSQL and MySQL allow
|
||||
multiple NULLs in a UNIQUE constraint, so a plain UNIQUE works for mobile_no — multiple
|
||||
users with no mobile remain valid.
|
||||
|
||||
Note: if your existing data already contains duplicate emails or mobiles, this migration
|
||||
WILL fail. Dedupe first:
|
||||
SELECT LOWER(email), COUNT(*) FROM m_appuser GROUP BY LOWER(email) HAVING COUNT(*) > 1;
|
||||
SELECT mobile_no, COUNT(*) FROM m_appuser WHERE mobile_no IS NOT NULL GROUP BY mobile_no HAVING COUNT(*) > 1;
|
||||
-->
|
||||
|
||||
<changeSet id="0295_add_unique_email_to_m_appuser" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not>
|
||||
<indexExists tableName="m_appuser" indexName="uq_m_appuser_email"/>
|
||||
</not>
|
||||
</preConditions>
|
||||
<addUniqueConstraint tableName="m_appuser" columnNames="email"
|
||||
constraintName="uq_m_appuser_email"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0295_add_unique_mobile_no_to_m_appuser" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not>
|
||||
<indexExists tableName="m_appuser" indexName="uq_m_appuser_mobile_no"/>
|
||||
</not>
|
||||
</preConditions>
|
||||
<!-- Drop the plain non-unique index added in 0283 first — the unique constraint replaces it. -->
|
||||
<dropIndex tableName="m_appuser" indexName="idx_m_appuser_mobile_no"/>
|
||||
<addUniqueConstraint tableName="m_appuser" columnNames="mobile_no"
|
||||
constraintName="uq_m_appuser_mobile_no"/>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user