first time login

first time login logic and validations,
mazdak/UX-2114
Mazdak Gibran 2 weeks ago
parent fd1b5b1607
commit d94e715957

@ -11,7 +11,7 @@ export const routes: Routes = [
component: LoginComponent
},
{
path: 'changepassword',
path: 'first-login-change-password',
component: ChangePasswordComponent
},
{

@ -23,6 +23,10 @@
<div class="bg-primary bg-soft text-center py-3 mt-2">
<img src="assets/images/logo.png" class="img-fluid mb-2" height="120" width="150" alt="Logo">
</div>
<h2 class="text-center fw-semibold my-3">
aConnect
</h2>
<div class="card-body">
<form [formGroup]="loginForm" class="form-horizontal">
<div class="mb-3">
@ -46,7 +50,7 @@
</div>
</div>
<div class="mt-3 d-grid">
<button class="btn btn-primary waves-effect waves-light" type="submit" (click)="login()" [disabled]="loginForm.invalid">{{"login" | translate}}</button>
<button class="btn btn-primary waves-effect waves-light" type="button" (click)="login()" [disabled]="loginForm.invalid">{{"login" | translate}}</button>
</div>
</form>
</div>

@ -104,6 +104,16 @@ export class LoginComponent {
this.ucred.password = this.loginForm.get(FormConstants.PASSWORD)?.value;
this.ucred.userId = this.loginForm.get(FormConstants.USER_ID)?.value;
this.authService.authenticate(this.ucred).subscribe( (res: any) => {
const user = res?.user;
this.storageService.setItem('user', JSON.stringify(res));
if (res?.requiresPasswordChange || user?.firstLogin) {
console.log('First time login - redirecting to first-login-change-password');
this.router.navigate(['/first-login-change-password']);
} else {
console.log('Regular login - redirecting to dashboard');
this.router.navigate(['/home/dashboard']);
}
});
}

@ -23,52 +23,51 @@ export class AuthenticationService {
constructor(private buttonManagementService: ButtonManagementService, private httpService: HttpURIService, private router: Router, private credentialService: CredentialService, private i18nService: I18NService, private storageService: StorageService) {
}
authenticate(uCreds: UserCredentials): Observable<any> {
const observable = new Observable((observer: Observer<any>) => {
const userJson = this.storageService.getItem('user');
if (this.storageService.getItem('user') != null) {
this.i18nService.error(ErrorMessages.ALREADY_LOGGED_IN, []);
return;
return new Observable(); // empty
}
this.credentialService.setPorOrgacode(HiddenValues.POR_ORGACODE);
this.credentialService.setUserId(uCreds.userId);
this.credentialService.setPassword(uCreds.password);
this.storageService.setItem(FormConstants.POR_ORGACODE, HiddenValues.POR_ORGACODE);
this.storageService.setItem(FormConstants.USER_ID, uCreds.userId);
this.storageService.setItem(FormConstants.PASSWORD, uCreds.password);
this.httpService.requestPOST(URIKey.USER_LOGIN_URI, uCreds).subscribe((data: any) => {
return this.httpService.requestPOST(URIKey.USER_LOGIN_URI, uCreds).pipe(
tap((data: any) => {
if (!(data instanceof HttpErrorResponse)) {
data.authenticated = true;
this.i18nService.success(SuccessMessages.LOGIN_SUCCESSFULLY, []);
this.storageService.setItem('user', JSON.stringify(data));
this.credentialService.setToken(data.token);
this.credentialService.setUserType(data.role);
if (data.user.permissions) {
this.storageService.setItem('permission', data.user.permissions);
this.credentialService.setPermission(JSON.parse(data.user.permissions));
}
else{
} else {
this.storageService.setItem('permission', '[]');
this.credentialService.setPermission([]);
}
this.buttonManagementService.setButtonPermissions(this.credentialService.getPermission(), this.isAdminUser());
if(data.user.isFirstLogin){
this.router.navigate(["/changepassword"]);
} else {
this.router.navigate(["/home/dashboard"]);
}
this.onAuthenticationComplete.next(true);
observer.complete();
}
else {
this.onAuthenticationComplete.next(false);
observer.error(false);
})
);
}
});
});
return observable;
updateCredentialsAfterPasswordChange(newPassword: string) {
this.storageService.setItem(FormConstants.PASSWORD, newPassword);
this.credentialService.setPassword(newPassword);
const userStr = this.storageService.getItem('user');
if (userStr) {
const user = JSON.parse(userStr);
user.authenticated = true;
this.storageService.setItem('user', JSON.stringify(user));
}
}
isAuthenticated(): boolean {

@ -6,6 +6,7 @@ import { AuthenticationService } from '../../services/authenticate.service';
import { CredentialService } from '../../services/credential.service';
import { FormConstants } from '../../utils/enums';
import { ButtonManagementService } from '../../services/button-management.service';
import { StorageService } from '../services/storage.service';
@Injectable(
@ -13,26 +14,47 @@ import { ButtonManagementService } from '../../services/button-management.servic
)
export class AuthenticationGuard implements CanActivate {
constructor(private router: Router, private authService: AuthenticationService, private location: LocationStrategy, private credentialService: CredentialService,private buttonManagementService: ButtonManagementService) { }
constructor(private router: Router, private authService: AuthenticationService, private location: LocationStrategy, private credentialService: CredentialService,private buttonManagementService: ButtonManagementService, private storageService: StorageService) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (state.url.includes('first-login-change-password')) {
return true;
}
if (typeof window !== 'undefined' && window.localStorage) {
let data = JSON.parse(window.localStorage.getItem('user') || '{}') as AuthenticationResponse;
let permission = JSON.parse(window.localStorage.getItem('permission') || '[]');
const userStr = this.storageService.getItem('user');
if (!userStr) {
this.authService.logout();
return false;
}
const data = JSON.parse(userStr);
if ((data?.requiresPasswordChange || data?.user?.firstLogin) &&
!state.url.includes('changePassword')) {
this.router.navigate(['/first-login-change-password']);
return false;
}
if (this.authService.isAuthenticated()) {
this.credentialService.setPorOrgacode(window.localStorage.getItem(FormConstants.POR_ORGACODE) || '');
this.credentialService.setUserId(window.localStorage.getItem(FormConstants.USER_ID) || '');
this.credentialService.setPassword(window.localStorage.getItem(FormConstants.PASSWORD) || '');
this.credentialService.setToken(data.token);
this.credentialService.setUserType(data.user.role);
let permission = JSON.parse(window.localStorage.getItem('permission') || '[]');
this.credentialService.setPermission(permission);
this.buttonManagementService.setButtonPermissions(this.credentialService.getPermission(), this.authService.isAdminUser());
this.authService.onAuthenticationComplete.next(true);
return true;
} else {
}
else {
this.authService.logout();
return false;
}
}
return false;
}

@ -16,16 +16,19 @@
</div>
<div class="card-body">
<form [formGroup]="firstTimeLoginForm">
<div class="w-100">
<label for="newPassword" class="form-label">
{{"oldPassword" | translate}}<span class="mandatory">*</span>
</label>
<div class="mb-3 w-100">
<div class="password-wrapper">
<input type="text" id="oldPassword" class="form-control" formControlName="oldPassword" type="{{passwordType}}"
<input id="oldPassword" class="form-control" formControlName="oldPassword" type="{{passwordType}}"
placeholder="{{ 'oldPassword' | translate }}" appNoWhitespaces />
<app-password-hide-show #psh class="password-eye align-items-stretch" [showPassword]="true"
(onEyeClick)="togglePasswordType()"></app-password-hide-show>
</div>
<div class="text-danger"
*ngIf="changePasswordForm.get('oldPassword')?.touched && changePasswordForm.get('oldPassword')?.invalid">
*ngIf="firstTimeLoginForm.get('oldPassword')?.touched && firstTimeLoginForm.get('oldPassword')?.invalid">
{{ 'fieldRequired' | translate }}
</div>
</div>
@ -34,12 +37,27 @@
<label for="newPassword" class="form-label">
{{"newPasswordStatic" | translate}}<span class="mandatory">*</span>
</label>
<div class="input-group auth-pass-inputgroup">
<div class="w-100">
<div class="password-wrapper">
<input type="{{passwordType1}}" class="form-control" formControlName="newPassword" id="newPassword"
placeholder="{{'enterNewPassword' | translate}}" appNoWhitespaces>
<app-password-hide-show #psh1 [showPassword]="true"
<app-password-hide-show #psh1 [showPassword]="true" class="password-eye align-items-stretch"
(onEyeClick)="togglePasswordType1()">
</app-password-hide-show>
</div>
<div class="text-danger" *ngIf="firstTimeLoginForm.get('newPassword')?.touched ">
<div *ngIf="firstTimeLoginForm.get('newPassword')?.hasError('required')">
{{ 'fieldRequired' | translate }}
</div>
<div *ngIf="firstTimeLoginForm.get('newPassword')?.hasError('pattern')">
{{ 'passwordPattern' | translate }}
</div>
<div *ngIf="firstTimeLoginForm.hasError('oldAndNewPasswordSame')">
{{'oldAndNewPass' | translate}}
</div>
</div>
</div>
</div>
@ -48,13 +66,24 @@
<label for="confirmPassword" class="form-label">
{{"confirmPassword" | translate}}<span class="mandatory">*</span>
</label>
<div class="input-group auth-pass-inputgroup">
<div class="w-100">
<div class="password-wrapper">
<input type="{{passwordType2}}" class="form-control" formControlName="confirmPassword" id="confirmPassword"
placeholder="{{'confirmPassword' | translate}}" appNoWhitespaces>
<app-password-hide-show #psh2 [showPassword]="true"
<app-password-hide-show #psh2 [showPassword]="true" class="password-eye align-items-stretch"
(onEyeClick)="togglePasswordType2()">
</app-password-hide-show>
</div>
<div class="text-danger" *ngIf="firstTimeLoginForm.get('confirmPassword')?.touched ">
<div *ngIf="firstTimeLoginForm.get('confirmPassword')?.hasError('required')">
{{ 'fieldRequired' | translate }}
</div>
<div *ngIf="firstTimeLoginForm.hasError('passwordMismatch')">
{{ 'passwordsDoNotMatch' | translate }}
</div>
</div>
</div>
</div>
<!-- Submit Button -->
@ -155,7 +184,7 @@
{{ 'passwordPattern' | translate }}
</div>
<div *ngIf="changePasswordForm.hasError('oldAndNewPasswordSame')">
Old password and new password cannot be the same
{{'oldAndNewPass' | translate}}
</div>

@ -8,7 +8,10 @@ import { URIKey } from '../../utils/uri-enums';
import { StorageService } from '../../shared/services/storage.service';
import { I18NService } from '../../services/i18n.service';
import { HttpErrorResponse } from '@angular/common/http';
import { SuccessMessages } from '../../utils/enums';
import { FormConstants, SuccessMessages } from '../../utils/enums';
import { Router } from '@angular/router';
import { AuthenticationService } from '../../services/authenticate.service';
@Component({
selector: 'app-change-password',
@ -31,7 +34,13 @@ passwordType2: string = 'password';
@ViewChild('psh') passwordHideShow?: PasswordHideShowComponent;
@ViewChild('psh1') passwordHideShow1 ?: PasswordHideShowComponent;
@ViewChild('psh2') passwordHideShow2 ?: PasswordHideShowComponent;
constructor(private fb: FormBuilder, private httpURIService: HttpURIService, private storageService: StorageService, private i18nService: I18NService){}
constructor(
private fb: FormBuilder,
private httpURIService: HttpURIService,
private storageService: StorageService,
private i18nService: I18NService,
private router: Router,
private authService: AuthenticationService ){}
togglePasswordType() {
this.passwordType = this.passwordHideShow?.showPassword ? 'password' : 'text';
@ -44,7 +53,11 @@ constructor(private fb: FormBuilder, private httpURIService: HttpURIService, pri
}
ngOnInit(): void {
this.checkIfFirstTimeChangePasswordOrNot();
const userStr = this.storageService.getItem('user');
if (userStr) {
const user = JSON.parse(userStr);
this.isFirstLogin = user?.requiresPasswordChange || user?.user?.firstLogin || false;
}
if (this.isFirstLogin) {
this.firstTimeLoginForm = this.fb.group({
@ -61,7 +74,10 @@ constructor(private fb: FormBuilder, private httpURIService: HttpURIService, pri
Validators.maxLength(20),
Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/)
]]
}, { validators: this.passwordMatchValidator });
}, { validators: [
this.passwordMatchValidator,
this.oldAndNewPasswordNotSame,]
});
}
else {
@ -135,6 +151,11 @@ constructor(private fb: FormBuilder, private httpURIService: HttpURIService, pri
};
}
onSubmit(){
if (this.isFirstLogin) {
this.changeFirstTimePassword();
} else {
this.changeRegularPassword();
}
const payload = this.getFormPayload();
this.httpURIService.requestPOST(URIKey.CHANGE_PASSWORD_URI, payload)
@ -142,6 +163,7 @@ constructor(private fb: FormBuilder, private httpURIService: HttpURIService, pri
next: (response) => {
if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.CHANGE_PASSWORD_SUCCESS, []);
this.router.navigate(['/dashboard']);
}
}
});
@ -149,4 +171,83 @@ constructor(private fb: FormBuilder, private httpURIService: HttpURIService, pri
}
changeFirstTimePassword() {
if (!this.firstTimeLoginForm || this.firstTimeLoginForm.invalid) {
if (this.firstTimeLoginForm) {
this.markFormGroupTouched(this.firstTimeLoginForm);
}
return;
}
const payload = this.getFormPayload();
this.httpURIService.requestPOST(URIKey.CHANGE_PASSWORD_URI, payload)
.subscribe({
next: (response) => {
if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.CHANGE_PASSWORD_SUCCESS, []);
const userStr = this.storageService.getItem('user');
if (userStr) {
const user = JSON.parse(userStr);
user.requiresPasswordChange = false;
if (user.user) {
user.user.firstLogin = false;
}
if (payload.newPassword) {
this.storageService.setItem(FormConstants.PASSWORD, payload.newPassword);
}
this.storageService.setItem('user', JSON.stringify(user));
this.authService.updateCredentialsAfterPasswordChange(payload.newPassword);
}
this.router.navigate(['/home/dashboard']);
}
},
error: (error) => {
console.error('Password change failed:', error);
}
});
}
changeRegularPassword() {
if (!this.changePasswordForm || this.changePasswordForm.invalid) {
if (this.changePasswordForm) {
this.markFormGroupTouched(this.changePasswordForm);
}
return;
}
const payload = this.getFormPayload();
this.httpURIService.requestPOST(URIKey.CHANGE_PASSWORD_URI, payload)
.subscribe({
next: (response) => {
if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.CHANGE_PASSWORD_SUCCESS, []);
this.router.navigate(['/home/dashboard']);
}
},
error: (error) => {
console.error('Password change failed:', error);
if (error.status === 401) {
this.authService.logout();
}
}
});
}
private markFormGroupTouched(formGroup: FormGroup) {
Object.values(formGroup.controls).forEach(control => {
control.markAsTouched();
if (control instanceof FormGroup) {
this.markFormGroupTouched(control);
}
});
}
}

@ -9,6 +9,7 @@ import { StorageService } from '../../shared/services/storage.service';
import { I18NService } from '../../services/i18n.service';
import { ErrorMessages, SuccessMessages } from '../../utils/enums';
import { HttpErrorResponse } from '@angular/common/http';
import { Router } from '@angular/router';
@Component({
selector: 'app-reset-password',
@ -24,7 +25,7 @@ export class ResetPasswordComponent implements OnInit{
@ViewChild('psh1') passwordHideShow1?: PasswordHideShowComponent;
@ViewChild('psh2') passwordHideShow2?: PasswordHideShowComponent;
constructor(private fb: FormBuilder, private httpURIService: HttpURIService, private storageService: StorageService, private i18nService: I18NService){}
constructor(private fb: FormBuilder, private httpURIService: HttpURIService, private storageService: StorageService, private i18nService: I18NService, private router: Router){}
ngOnInit(): void {
const userIdValue = this.storageService.getItem('USER_ID')
@ -93,6 +94,7 @@ export class ResetPasswordComponent implements OnInit{
next: (response) => {
if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.RESET_PASSWORD_SUCCESS, []);
this.router.navigate(['/dashboard']);
}
}
});

@ -265,5 +265,6 @@
"nameMinLength" : "Name must be at least 5 characters",
"emptySpaceRestriction" : "Empty spaces are not allowed",
"USER_CREATED_SUCCESS": "User Created",
"USER_DELETE_SUCCESS": "User Deleted"
"USER_DELETE_SUCCESS": "User Deleted",
"oldAndNewPass":"Old password and new password cannot be the same"
}
Loading…
Cancel
Save