/** * 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 { return this.http.post('/login/otp/generate', { username }); } verifyMobileOtp(otpId: number, otp: string): Observable { return this.http.post('/login/otp/verify', { otpId, otp }); } // ───── Email OTP (SMTP) ───── sendEmailOtp(username: string): Observable { return this.http.post('/login/otp/email/generate', { username }); } verifyEmailOtp(otpId: number, otp: string): Observable { return this.http.post('/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 { return this.http.get('/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 { return this.http.post('/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 }); } }