Skip to content

Commit 06fcb7a

Browse files
authored
Merge pull request #7010 from microting/copilot/fix-7009
Implement @angular-material-extensions/password-strength for password security validation
2 parents e083b10 + fefd989 commit 06fcb7a

File tree

10 files changed

+155
-8
lines changed

10 files changed

+155
-8
lines changed

INTEGRATION_STEPS.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Angular Material Extensions Password Strength Integration
2+
3+
## ✅ Integration Complete
4+
5+
The password strength meter has been successfully integrated with the following configuration:
6+
7+
### Configuration Applied
8+
- `enableLengthRule`: true
9+
- `enableLowerCaseLetterRule`: true
10+
- `enableUpperCaseLetterRule`: true
11+
- `enableDigitRule`: true
12+
- `enableSpecialCharRule`: false *(disabled per requirements)*
13+
- `min`: 8 *(minimum password length)*
14+
- `max`: 50
15+
16+
### Components Updated
17+
1.**User Set Password Modal** (`user-set-password.component.*`) - Admin password setting
18+
2.**Change Password** (`change-password.component.*`) - User profile password change
19+
3.**Restore Password Confirmation** (`restore-password-confirmation.component.*`) - Password reset flow
20+
21+
### Implementation Details
22+
- ✅ Package installed: `@angular-material-extensions/[email protected]`
23+
- ✅ Module imports activated in `account-management.module.ts` and `auth.module.ts`
24+
- ✅ HTML templates updated with password strength meters
25+
- ✅ TypeScript methods implemented for strength tracking
26+
- ✅ Form validation updated to require minimum 8 characters
27+
- ✅ Special character requirements disabled as requested
28+
29+
### Features Implemented
30+
- **Real-time password strength visualization**: Color-coded strength indicators
31+
- **Configurable validation rules**: Length, lowercase, uppercase, digits (special chars disabled)
32+
- **Strength scoring**: 0-100 scale with event handling
33+
- **Form validation integration**: Weak password validation (< 40 strength)
34+
- **Material Design integration**: Seamless visual integration
35+
36+
## Testing Recommendations
37+
1. Test each password field for visual feedback
38+
2. Verify strength scoring works correctly (0-100 scale)
39+
3. Test form validation integration with weak passwords
40+
4. Ensure all password requirements are enforced except special characters
41+
42+
## Example Usage
43+
```html
44+
<mat-password-strength
45+
[password]="form.get('newPassword')?.value || ''"
46+
[enableLengthRule]="true"
47+
[enableLowerCaseLetterRule]="true"
48+
[enableUpperCaseLetterRule]="true"
49+
[enableDigitRule]="true"
50+
[enableSpecialCharRule]="false"
51+
[min]="8"
52+
[max]="50"
53+
(onStrengthChanged)="onPasswordStrengthChanged($event)">
54+
</mat-password-strength>
55+
```

eform-client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"@angular/platform-browser": "20.1.2",
7272
"@angular/platform-browser-dynamic": "20.1.2",
7373
"@angular/router": "20.1.2",
74+
"@angular-material-extensions/password-strength": "^16.0.0",
7475
"@ng-matero/extensions": "20.2.1",
7576
"@ngrx/effects": "19.2.1",
7677
"@ngrx/entity": "19.2.1",

eform-client/src/app/modules/account-management/account-management.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {MatDialogModule} from '@angular/material/dialog';
2323
import {MatInputModule} from '@angular/material/input';
2424
import {MatIconModule} from '@angular/material/icon';
2525
import {FileUploadModule} from "ng2-file-upload";
26+
import {MatPasswordStrengthModule} from '@angular-material-extensions/password-strength';
2627

2728
@NgModule({
2829
imports: [
@@ -44,6 +45,7 @@ import {FileUploadModule} from "ng2-file-upload";
4445
MatInputModule,
4546
MatIconModule,
4647
FileUploadModule,
48+
MatPasswordStrengthModule,
4749
],
4850
declarations: [
4951
ChangePasswordComponent,

eform-client/src/app/modules/account-management/components/profile/change-password/change-password.component.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,20 @@
3535
autocomplete="new-password"
3636
/>
3737
</mat-form-field>
38+
39+
<!-- Password Strength Meter -->
40+
<mat-password-strength
41+
[password]="changePasswordForm.get('newPassword')?.value || ''"
42+
[enableLengthRule]="true"
43+
[enableLowerCaseLetterRule]="true"
44+
[enableUpperCaseLetterRule]="true"
45+
[enableDigitRule]="true"
46+
[enableSpecialCharRule]="false"
47+
[min]="8"
48+
[max]="50"
49+
(onStrengthChanged)="onPasswordStrengthChanged($event)">
50+
</mat-password-strength>
51+
3852
<mat-form-field>
3953
<mat-label>{{ 'New password confirmation' | translate }}</mat-label>
4054
<input

eform-client/src/app/modules/account-management/components/profile/change-password/change-password.component.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import { FormBuilder, FormGroup, Validators} from '@angular/forms';
1212
export class ChangePasswordComponent implements OnInit {
1313
changePasswordModel: ChangePasswordModel = new ChangePasswordModel();
1414
changePasswordForm: FormGroup;
15+
passwordStrength = 0; // Track password strength score
1516
constructor(private authService: AuthService, private fb: FormBuilder) {
1617
this.changePasswordForm = this.fb.group({
17-
oldPassword: ['', [Validators.required, Validators.min(8)]],
18-
newPassword: ['', [Validators.required, Validators.min(8)]],
19-
confirmPassword: ['', [Validators.required, Validators.min(8)/*, this.checkPasswords*/]]
18+
oldPassword: ['', [Validators.required, Validators.minLength(8)]],
19+
newPassword: ['', [Validators.required, Validators.minLength(8)]],
20+
confirmPassword: ['', [Validators.required, Validators.minLength(8)/*, this.checkPasswords*/]]
2021
});
2122
}
2223

@@ -37,6 +38,20 @@ export class ChangePasswordComponent implements OnInit {
3738
);
3839
}
3940

41+
onPasswordStrengthChanged(strength: number): void {
42+
this.passwordStrength = strength;
43+
// Optionally add additional validation based on strength
44+
const passwordControl = this.changePasswordForm.get('newPassword');
45+
if (passwordControl && strength < 40) {
46+
passwordControl.setErrors({ ...passwordControl.errors, weakPassword: true });
47+
} else if (passwordControl && passwordControl.hasError('weakPassword')) {
48+
delete passwordControl.errors.weakPassword;
49+
if (Object.keys(passwordControl.errors).length === 0) {
50+
passwordControl.setErrors(null);
51+
}
52+
}
53+
}
54+
4055
/* checkPasswords(group: FormGroup) {
4156
let pass = group.get('newPassword').value;
4257
let confirmPass = group.get('confirmPassword').value;

eform-client/src/app/modules/account-management/components/users/user-set-password-modal/user-set-password.component.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,20 @@ <h3 mat-dialog-title>{{'Set user password' | translate}}</h3>
3737
{{newPasswordVisible ? 'visibility_off' : 'visibility'}}
3838
</mat-icon>
3939
</mat-form-field>
40+
41+
<!-- Password Strength Meter -->
42+
<mat-password-strength
43+
[password]="setPasswordForm.get('newPassword')?.value || ''"
44+
[enableLengthRule]="true"
45+
[enableLowerCaseLetterRule]="true"
46+
[enableUpperCaseLetterRule]="true"
47+
[enableDigitRule]="true"
48+
[enableSpecialCharRule]="false"
49+
[min]="8"
50+
[max]="50"
51+
(onStrengthChanged)="onPasswordStrengthChanged($event)">
52+
</mat-password-strength>
53+
4054
<mat-form-field>
4155
<mat-label>{{ 'New password confirmation' | translate }}</mat-label>
4256
<input

eform-client/src/app/modules/account-management/components/users/user-set-password-modal/user-set-password.component.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ export class UserSetPasswordComponent implements OnInit {
1616
userPasswordSet: EventEmitter<UserInfoModel> = new EventEmitter<UserInfoModel>();
1717
newPasswordVisible = false;
1818
confirmPasswordVisible = false;
19+
passwordStrength = 0; // Track password strength score
1920
constructor(private authService: AuthService,
2021
private fb: FormBuilder,
2122
public dialogRef: MatDialogRef<UserSetPasswordComponent>,
2223
@Inject(MAT_DIALOG_DATA) public selectedUser: UserInfoModel = new UserInfoModel()) {
2324
this.setPasswordForm = this.fb.group({
24-
newPassword: ['', [Validators.minLength(6)]],
25-
confirmPassword: ['', [Validators.minLength(6)]]
25+
newPassword: ['', [Validators.minLength(8)]],
26+
confirmPassword: ['', [Validators.minLength(8)]]
2627
});
2728
}
2829

@@ -55,4 +56,18 @@ export class UserSetPasswordComponent implements OnInit {
5556
toggleConfirmPasswordVisibility() {
5657
this.confirmPasswordVisible = !this.confirmPasswordVisible;
5758
}
59+
60+
onPasswordStrengthChanged(strength: number): void {
61+
this.passwordStrength = strength;
62+
// Optionally add additional validation based on strength
63+
const passwordControl = this.setPasswordForm.get('newPassword');
64+
if (passwordControl && strength < 40) {
65+
passwordControl.setErrors({ ...passwordControl.errors, weakPassword: true });
66+
} else if (passwordControl && passwordControl.hasError('weakPassword')) {
67+
delete passwordControl.errors.weakPassword;
68+
if (Object.keys(passwordControl.errors).length === 0) {
69+
passwordControl.setErrors(null);
70+
}
71+
}
72+
}
5873
}

eform-client/src/app/modules/auth/auth.module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {MatIconModule} from '@angular/material/icon';
2020
import {MatButtonModule} from '@angular/material/button';
2121
import {MatTooltipModule} from '@angular/material/tooltip';
2222
import {MtxSelectModule} from '@ng-matero/extensions/select';
23+
import {MatPasswordStrengthModule} from '@angular-material-extensions/password-strength';
2324

2425

2526
@NgModule({
@@ -36,7 +37,8 @@ import {MtxSelectModule} from '@ng-matero/extensions/select';
3637
MatIconModule,
3738
MatButtonModule,
3839
MatTooltipModule,
39-
MtxSelectModule
40+
MtxSelectModule,
41+
MatPasswordStrengthModule,
4042
],
4143
declarations: [
4244
LoginComponent,

eform-client/src/app/modules/auth/components/auth/restore-password-confirmation/restore-password-confirmation.component.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,20 @@
2121
<mat-error *ngIf="form.controls['newPassword'].hasError('required')">{{'New Password field is required'| translate}}</mat-error>
2222
<mat-error *ngIf="form.controls['newPassword'].hasError('minlength')">{{'New Password field must contain at least ' + this.form.controls['newPassword'].errors['minlength'].requiredLength + ' characters.'| translate}}</mat-error>
2323
</mat-form-field>
24+
25+
<!-- Password Strength Meter -->
26+
<mat-password-strength
27+
[password]="form.get('newPassword')?.value || ''"
28+
[enableLengthRule]="true"
29+
[enableLowerCaseLetterRule]="true"
30+
[enableUpperCaseLetterRule]="true"
31+
[enableDigitRule]="true"
32+
[enableSpecialCharRule]="false"
33+
[min]="8"
34+
[max]="50"
35+
(onStrengthChanged)="onPasswordStrengthChanged($event)">
36+
</mat-password-strength>
37+
2438
<mat-form-field class="mb-4">
2539
<mat-label>{{'Confirm Password' | translate}}</mat-label>
2640
<input
@@ -41,7 +55,7 @@
4155
<mat-error *ngIf="form.controls['newPasswordConfirm'].hasError('required')">{{'Confirm Password field is required'| translate}}</mat-error>
4256
<mat-error *ngIf="form.controls['newPasswordConfirm'].hasError('passwordsNotEqual')">{{'Passwords not equal in New Password and Confirm Password fields'| translate}}</mat-error>
4357
</mat-form-field>
44-
<p>{{ 'The password must contain at least 6 characters.' | translate }}</p>
58+
<p>{{ 'The password must contain at least 8 characters.' | translate }}</p>
4559
</div>
4660
<button
4761
mat-raised-button

eform-client/src/app/modules/auth/components/auth/restore-password-confirmation/restore-password-confirmation.component.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export class RestorePasswordConfirmationComponent implements OnInit, OnDestroy {
2525
form: FormGroup;
2626
newPasswordVisible = false;
2727
newPasswordConfirmVisible = false;
28+
passwordStrength = 0; // Track password strength score
2829

2930
constructor(
3031
private translateService: TranslateService,
@@ -41,7 +42,7 @@ export class RestorePasswordConfirmationComponent implements OnInit, OnDestroy {
4142
console.debug('RestorePasswordConfirmationComponent - ngOnInit');
4243
this.route.queryParams.subscribe((params) => {
4344
this.form = this.fb.group({
44-
newPassword: ['', [Validators.required, Validators.minLength(6)]],
45+
newPassword: ['', [Validators.required, Validators.minLength(8)]],
4546
newPasswordConfirm: ['', [Validators.required]],
4647
userId: [params['userId']],
4748
code: [params['code']],
@@ -86,4 +87,18 @@ export class RestorePasswordConfirmationComponent implements OnInit, OnDestroy {
8687
toggleNewPasswordConfirmVisibility() {
8788
this.newPasswordConfirmVisible = !this.newPasswordConfirmVisible;
8889
}
90+
91+
onPasswordStrengthChanged(strength: number): void {
92+
this.passwordStrength = strength;
93+
// Optionally add additional validation based on strength
94+
const passwordControl = this.form.get('newPassword');
95+
if (passwordControl && strength < 40) {
96+
passwordControl.setErrors({ ...passwordControl.errors, weakPassword: true });
97+
} else if (passwordControl && passwordControl.hasError('weakPassword')) {
98+
delete passwordControl.errors.weakPassword;
99+
if (Object.keys(passwordControl.errors).length === 0) {
100+
passwordControl.setErrors(null);
101+
}
102+
}
103+
}
89104
}

0 commit comments

Comments
 (0)