feat(users): OTP delivery method picker on user creation + post-login agreement
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
Tests / testRigor (push) Canceled after 0s

Create User dialog now has a mandatory Email/Mobile OTP delivery method
picker (styled as a pill toggle) alongside the existing fields.

Adds a new post-login agreement acceptance dialog: right after the existing
2FA flow (TwoFactorAuthenticationComponent) succeeds, or immediately after
login for users without 2FA, the user must accept the bank's current
agreement/disclaimer at least once before reaching the app.

Also adds a "Manage User Agreement" admin screen (System section) to publish
versioned agreement text without a code deploy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 16:20:46 +05:30
co-authored by Claude Sonnet 5
parent d0536dbb11
commit d334dd1669
18 changed files with 1356 additions and 16 deletions
@@ -281,12 +281,8 @@ export class AuthenticationService {
message: 'Your password has expired, please reset your password!'
});
} else {
this.setCredentials(credentials);
this.alertService.alert({
type: 'Authentication Success',
message: `${credentials.username} successfully logged in!`
});
delete this.credentials;
this.credentials = credentials;
this.finalizeLogin();
}
}
}
@@ -546,16 +542,60 @@ export class AuthenticationService {
message: 'Your password has expired, please reset your password!'
});
} else {
this.setCredentials(this.credentials);
this.alertService.alert({
type: 'Authentication Success',
message: `${this.credentials.username} successfully logged in!`
});
delete this.credentials;
this.storage.setItem(this.twoFactorAuthenticationTokenStorageKey, JSON.stringify(response));
this.finalizeLogin();
}
}
/**
* Runs after primary login (and 2FA, if the account has it enrolled) succeeds. Every user must
* accept the bank's current agreement/disclaimer at least once — checks that here and either
* finishes the login immediately (already accepted, or nothing published yet) or fires an alert
* so `login.component.ts` can show the accept dialog.
*/
private finalizeLogin(): void {
this.http.get<{ accepted: boolean }>('/useragreements/active/status').subscribe({
next: (status) => {
if (status?.accepted) {
this.completeAuthentication();
} else {
this.alertService.alert({
type: 'Agreement Acceptance Required',
message: 'Please review and accept the agreement to continue.'
});
}
},
// Don't let an agreement-status hiccup block login entirely.
error: () => this.completeAuthentication()
});
}
/** Finishes the login: persists credentials and fires the existing 'Authentication Success' alert. */
private completeAuthentication(): void {
this.setCredentials(this.credentials);
this.alertService.alert({
type: 'Authentication Success',
message: `${this.credentials.username} successfully logged in!`
});
delete this.credentials;
}
/** Fetches the active agreement's text for display, when the current user hasn't accepted it yet. */
getAgreementStatus(): Observable<{
accepted: boolean;
id?: number;
version?: number;
title?: string;
body?: string;
}> {
return this.http.get<any>('/useragreements/active/status');
}
/** Records the current user's acceptance of the active agreement, then finishes the login. */
acceptActiveAgreement(): Observable<void> {
return this.http.post<any>('/useragreements/active/accept', {}).pipe(map(() => this.completeAuthentication()));
}
/**
* Resets the user's password and authenticates the user.
* @param {any} passwordDetails New password.