feat: MFA/2FA, OTP audit logs, KYC + client/user enhancements
- Access Control module: SMS/Email/TOTP/Hardware-token MFA factor management - OTP Audit Logs module under Admin menu (list + detail) with 401/403 handling - Two-factor authentication login flow updates - Client KYC, India address, family-members, ckyc, nominee enhancements - New shared KYC helpers (kyc-proof-types, kyc-upload-helper.service) - User create/edit/view enhancements - Shell navigation: OTP Audit Logs entry under real Admin menu
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export interface OtpGenerateResponse {
|
||||
success: boolean;
|
||||
otpId?: number;
|
||||
otpReference?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface OtpVerifyResponse {
|
||||
verified: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export type MfaFactorType = 'SMS' | 'EMAIL' | 'TOTP' | 'TOKEN';
|
||||
|
||||
export interface MfaFactor {
|
||||
id?: number;
|
||||
factorType: MfaFactorType;
|
||||
destination?: string;
|
||||
secret?: string;
|
||||
serial?: string;
|
||||
verified: boolean;
|
||||
verifiedOn?: string;
|
||||
otpLogId?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the login-OTP endpoints (LincPay SMS + SMTP email) plus the
|
||||
* MFA-factor persistence endpoints used by Access Control.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AccessControlService {
|
||||
private http = inject(HttpClient);
|
||||
|
||||
// ───── Mobile OTP (LincPay) ─────
|
||||
sendMobileOtp(username: string): Observable<OtpGenerateResponse> {
|
||||
return this.http.post<OtpGenerateResponse>('/login/otp/generate', { username });
|
||||
}
|
||||
|
||||
verifyMobileOtp(otpId: number, otp: string): Observable<OtpVerifyResponse> {
|
||||
return this.http.post<OtpVerifyResponse>('/login/otp/verify', { otpId, otp });
|
||||
}
|
||||
|
||||
// ───── Email OTP (SMTP) ─────
|
||||
sendEmailOtp(username: string): Observable<OtpGenerateResponse> {
|
||||
return this.http.post<OtpGenerateResponse>('/login/otp/email/generate', { username });
|
||||
}
|
||||
|
||||
verifyEmailOtp(otpId: number, otp: string): Observable<OtpVerifyResponse> {
|
||||
return this.http.post<OtpVerifyResponse>('/login/otp/email/verify', { otpId, otp });
|
||||
}
|
||||
|
||||
// ───── MFA factors persistence (NEW backend endpoints) ─────
|
||||
/**
|
||||
* Marker header that tells our Fineract security filter to return a plain
|
||||
* 401 (no `WWW-Authenticate: Basic`) so the browser doesn't pop up its
|
||||
* native Basic-Auth dialog when the endpoint isn't wired up yet.
|
||||
*/
|
||||
private silentHeaders = new HttpHeaders({ 'X-Requested-With': 'XMLHttpRequest' });
|
||||
|
||||
/** List the signed-in user's MFA factors. */
|
||||
listMyFactors(): Observable<MfaFactor[]> {
|
||||
return this.http.get<MfaFactor[]>('/mfa/factors', { headers: this.silentHeaders });
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist a verified factor. Backend should set `verified=true`,
|
||||
* `verified_on=NOW()`, and link to the matching `m_otp_log` row.
|
||||
*/
|
||||
saveFactor(payload: {
|
||||
factorType: MfaFactorType;
|
||||
destination?: string;
|
||||
secret?: string;
|
||||
serial?: string;
|
||||
otpId?: number;
|
||||
}): Observable<MfaFactor> {
|
||||
return this.http.post<MfaFactor>('/mfa/factors', payload, { headers: this.silentHeaders });
|
||||
}
|
||||
|
||||
/** Disable a factor (logical delete — sets `disabled_on`). */
|
||||
disableFactor(factorId: number): Observable<{ resourceId: number }> {
|
||||
return this.http.delete<{ resourceId: number }>(`/mfa/factors/${factorId}`, {
|
||||
headers: this.silentHeaders
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user