Code development platform for open source projects from the European Union institutions :large_blue_circle: EU Login authentication by SMS has been phased out. To see alternatives please check here

Skip to content
Snippets Groups Projects
Commit 2391e3a6 authored by Joze RIHTARSIC's avatar Joze RIHTARSIC
Browse files

Pull request #71: [EDELIVERY-12749] Reset password feature and refactoring of...

Pull request #71: [EDELIVERY-12749] Reset password feature and refactoring of the email messages templates.

Merge in EDELIVERY/smp from feature/EDELIVERY-12749-user-management-user-reset-password-via-email-link to development

* commit '7871bf84':
  [EDELIVERY-12749] Reset password feature and refactoring of the email messages templates.
parents a90a630d 7871bf84
Branches
Tags
No related merge requests found
Pipeline #140608 failed
Showing
with 421 additions and 111 deletions
eDelivery SMP 5.1
- Added the HTTP parameter 'Resource-Owner' as alternative to ServiceGroup-Owner
- added new properties:
smp.instance.name: The SMP instance name
smp.credentials.reset_request.url: The URL to reset the user password
smp.credentials.reset_request.url.validMinutes: The time in minutes the reset request is valid
eDelivery SMP 5.0
- removed: bdmsl.participant.multidomain.enabled
- environment properties have now 'smp.' prefix
......
......@@ -29,9 +29,9 @@ services:
volumes:
- ./properties/db-scripts:/tmp/custom-data/
- ./properties/keystores:/tmp/keystores/
# ports:
ports:
# - "3908:3306"
# - "8982:8080"
- "8982:8080"
# - "6902:6901"
# - "8953:53"
# - "5005:5005"
......@@ -43,11 +43,11 @@ services:
- ./eulogin/init-data:/resources/ecas-mock-server
# Map this folder to host to be able to change runtime data for manual testing!
# - ./eulogin/ecas-mock-server:/data/ecas-mock-server
# ports:
# - "7102:7102"
ports:
- "7102:7102"
mail-service:
image: inbucket/inbucket:3.0.0
hostname: mail-server.smp.local
# ports:
# - "9005:9000"
ports:
- "9005:9000"
......@@ -4,6 +4,13 @@
# running Docker images for the integration tests.
################################################################
FUNCTION_FOLDER="$(cd -P $( dirname "${BASH_SOURCE[0]}" ) && pwd)"
SMP_PROJECT_FOLDER=$(readlink -e "${FUNCTION_FOLDER}/../../..")
SMP_ARTEFACTS="${SMP_PROJECT_FOLDER}/smp-webapp/target"
SMP_SPRINGBOOT_ARTEFACTS="${SMP_PROJECT_FOLDER}/smp-springboot/target"
SMP_PLUGIN_EXAMPLE="${SMP_PROJECT_FOLDER}/smp-examples/smp-spi-payload-validation-example/target"
SMP_ARTEFACTS_CLEAR="false"
################################################################
# Function exports the DomiSMP build specific artefact names.
exportBuildArtefactNames() {
......@@ -34,6 +41,7 @@ function exportImageNames() {
function initializeCommonVariables() {
echo "initialize common variables"
exportImageNames
discoverApplicationVersion
BUILD_KEY=$(echo "${bamboo_buildResultKey:-test}" | tr '[:upper:]' '[:lower:]')
PLAN_PREFIX="${COMPOSE_PROJECT_NAME}"-${BUILD_KEY}
......
......@@ -31,7 +31,6 @@ ORA_VERSION="11.2.0.2"
ORA_EDITION="xe"
ORA_SERVICE="xe"
SMP_VERSION=
ORACLE_ARTEFACTS="/CEF/repo"
SMP_PROJECT_FOLDER=$(readlink -e "${WORKDIR}/../../..")
......@@ -61,8 +60,6 @@ while getopts v:o:a:s:c:p: option; do
esac
done
# discover SMP version
discoverApplicationVersion
echo "*****************************************************************"
echo "* SMP artefact folders: [$SMP_ARTEFACTS], (Clear folder after build: [$SMP_ARTEFACTS_CLEAR] )"
......
......@@ -4,12 +4,16 @@
# first it copies external resources to resources folder
# then it builds the image using docker-compose.build.yml
# and finally it cleans the external resources
WORKING_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${WORKING_DIR}"
e
source "${WORKING_DIR}/../../functions/common.functions"
initializeCommonVariables
: "${SMP_PROJECT_FOLDER:?Need to set SMP project folder non-empty!}"
: "${SMP_VERSION:?Need to set SMP version non-empty!}"
: "${SMP_ARTEFACTS:?Need to set SMP_ARTEFACTS non-empty!}"
WORKING_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${WORKING_DIR}"
copyExternalImageResources() {
echo "Copy test project resources ..."
......
......@@ -83,7 +83,6 @@ See the Licence for the specific language governing permissions and limitations
<cxf-xjc-runtime.version>3.3.2</cxf-xjc-runtime.version>
<cxf.version>3.5.7</cxf.version>
<ehcache.version>2.10.9.2</ehcache.version>
<freemarker.version>2.3.32</freemarker.version>
<h2.version>2.2.224</h2.version>
<hamcrest-junit.version>2.0.0.0</hamcrest-junit.version>
<hamcrest.version>2.2</hamcrest.version>
......@@ -256,11 +255,6 @@ See the Licence for the specific language governing permissions and limitations
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>${freemarker.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
......
......@@ -4,4 +4,7 @@ export interface SmpInfo {
authTypes?: string[];
ssoAuthenticationLabel?: string;
ssoAuthenticationURI?: string;
passwordValidationRegExp?: string;
passwordValidationRegExpMessage?: string;
}
......@@ -99,7 +99,7 @@ export class AppComponent {
onDrawerContentScroll(scrollEvent: any){
let scrollTop = scrollEvent.srcElement.scrollTop;
this.alertService.setSticky(scrollTop > 0)
this.alertService.setKeepAfterNavigationChange(scrollTop > 0)
}
}
......@@ -141,6 +141,7 @@ import {
} from "./system-settings/admin-properties/property-details-dialog/property-details-dialog.component";
import {UserAlertsComponent} from "./user-settings/user-alerts/user-alerts.component";
import {SmpEditorComponent} from "./common/components/smp-editor/smp-editor.component";
import {ResetCredentialComponent} from "./security/reset-credential/reset-credential.component";
@NgModule({
......@@ -198,6 +199,7 @@ import {SmpEditorComponent} from "./common/components/smp-editor/smp-editor.comp
PasswordChangeDialogComponent,
PropertyComponent,
PropertyDetailsDialogComponent,
ResetCredentialComponent,
ResourceDetailsDialogComponent,
ResourceDetailsPanelComponent,
ResourceDialogComponent,
......
......@@ -21,6 +21,7 @@ import {authorizeChildSystemAdminGuard} from "./guards/authorize-child-system-ad
import {activateChildResourceGuard} from "./guards/activate-child-document.guard";
import {UserAlertsComponent} from "./user-settings/user-alerts/user-alerts.component";
import {AdminAlertsComponent} from "./system-settings/admin-alerts/admin-alerts.component";
import {ResetCredentialComponent} from "./security/reset-credential/reset-credential.component";
const appRoutes: Routes = [
......@@ -28,6 +29,7 @@ const appRoutes: Routes = [
{path: '', component: ResourceSearchComponent},
{path: 'search', redirectTo: ''},
{path: 'login', component: LoginComponent},
{path: 'reset-credential/:resetToken', component: ResetCredentialComponent},
{
path: 'edit',
canActivateChild: [authenticationGuard],
......
<div #alertMessage class="mat-elevation-z8"
*ngIf="message" [ngClass]="{ 'alert-message': message,
'alert-success': message.type === 'success',
'alert-info': message.type === 'info',
'alert-warning': message.type === 'warning',
'alert-error': message.type === 'error'}"
id="alertmessage_id">
<span class="closebtn" (click)="clearAlert(true)">&times;</span>
......
......@@ -2,16 +2,21 @@ import {Component, OnDestroy, OnInit} from '@angular/core';
import {AlertMessageService} from './alert-message.service';
import {Subscription} from "rxjs";
/**
* This component is used to display alert messages/notifications on the top of the page in an overlay (growl).
* In case of success messages, the message will be displayed for a certain amount of time and it will automatically disappear
* unless sticky flat is set to true.
*
* The messages can be of different types: success, error, info, warning.
*/
@Component({
selector: 'alert',
templateUrl: './alert-message.component.html',
styleUrls: ['./alert-message.component.css']
})
export class AlertMessageComponent implements OnInit, OnDestroy {
readonly successTimeout: number = 3000;
message: any;
private subscription: Subscription;
......@@ -20,7 +25,9 @@ export class AlertMessageComponent implements OnInit, OnDestroy {
}
ngOnInit() {
this.subscription = this.alertService.getMessage().subscribe(message => { this.showMessage(message); });
this.subscription = this.alertService.getMessage().subscribe(message => {
this.showMessage(message);
});
}
ngOnDestroy(): void {
......@@ -33,10 +40,10 @@ export class AlertMessageComponent implements OnInit, OnDestroy {
showMessage(message: any) {
this.message = message;
if (message && message.type && message.type === 'success') {
if (message && message.timeoutInSeconds && message.timeoutInSeconds > 0) {
setTimeout(() => {
this.clearAlert();
}, this.successTimeout);
}, this.message.timeoutInSeconds * 1000);
}
}
}
import {Injectable} from '@angular/core';
import {NavigationEnd, NavigationStart, Router} from '@angular/router';
import {Observable, Subject} from 'rxjs';
import {HttpErrorResponse} from "@angular/common/http";
/**
* AlertMessageRO is the object that will be used to display the message in the SMP alert component in overlay.
* These messages are not the same to SMP alerts, which are used to display alert the page.
* It contains the type of the message (success, error, info, warning), the text of the message and the timeout in seconds.
*/
export interface AlertMessageRO {
type: string,
text: string,
timeoutInSeconds?: number
}
@Injectable()
export class AlertMessageService {
private subject = new Subject<any>();
private previousRoute = '';
// the default timeout duration
readonly DEFAULT_TIMEOUT: number = 3;
private sticky = false;
private message: { type: string, text: string };
private subject = new Subject<any>();
private previousRoute:string = '';
private keepAfterNavigationChange:boolean = false;
private message: AlertMessageRO;
//TODO move the logic in the ngInit block
constructor(private router: Router) {
// clear alert message on route change
router.events.subscribe(event => {
if (event instanceof NavigationStart) {
if (this.isRouteChanged(event.url)) {
this.clearAlert();
} else {
console.log('Alert after when navigating from [' + this.previousRoute + '] to [' + event.url + ']');
}
} else if (event instanceof NavigationEnd) {
this.previousRoute = event.url;
if (this.sticky) {
if (this.keepAfterNavigationChange) {
this.displayCurrentMessage();
this.reset();
}
......@@ -33,7 +43,7 @@ export class AlertMessageService {
}
private reset() {
this.sticky = false;
this.keepAfterNavigationChange = false;
this.message = null;
}
......@@ -50,32 +60,78 @@ export class AlertMessageService {
}
clearAlert(force = false): void {
if (!force && this.sticky) {
if (!force && this.keepAfterNavigationChange) {
return;
}
this.subject.next(null);
}
setSticky (sticky: boolean) {
this.sticky = sticky;
setKeepAfterNavigationChange(keepAfterNavigation: boolean) {
this.keepAfterNavigationChange = keepAfterNavigation;
}
displayCurrentMessage() {
this.subject.next(this.message);
}
success (message: string, keepAfterNavigationChange = false) {
this.setSticky(keepAfterNavigationChange);
this.message = {type: 'success', text: message};
this.displayCurrentMessage();
/**
* Extract the message from the object return it as a string. The object can be a string or an HttpErrorResponse
*
* @param messageObject
*/
getObjectMessage(messageObject: any): string {
let message = 'An error occurred';
if (typeof messageObject === 'string') {
return messageObject;
}
if (messageObject instanceof HttpErrorResponse) {
return this.getHttpErrorResponseMessage(messageObject);
}
}
error (message: string, keepAfterNavigationChange = false) {
this.setSticky(keepAfterNavigationChange);
this.message = {type: 'error', text: message};
/**
* Extract the message from the HttpErrorResponse and return it as a string
* @param httpErrorResponse
*/
getHttpErrorResponseMessage(httpErrorResponse: HttpErrorResponse): string {
let message: string;
if (httpErrorResponse.error) {
if (httpErrorResponse.error.errorDescription) {
message = httpErrorResponse.error.errorDescription;
} else if (httpErrorResponse.error.message) {
message = httpErrorResponse.error.message;
}
} else {
message = httpErrorResponse.statusText + ': ' + httpErrorResponse.message;
}
return message;
}
/**
* Show a message of a given type
* @param messageObject The message to display
* @param type The type of the message (success, error, info, warning)
* @param keepAfterNavigationChange If true, the message will be displayed after a navigation changed
*/
showMessage(messageObject: any, type: string, keepAfterNavigationChange:boolean = false, timeoutInSeconds: number = null) {
this.setKeepAfterNavigationChange(keepAfterNavigationChange);
this.message = {
type: type,
text: this.getObjectMessage(messageObject),
// if the timeoutInSeconds is not set, we use the default timeout for success messages
timeoutInSeconds: !timeoutInSeconds && type =="success" ?this.DEFAULT_TIMEOUT: timeoutInSeconds
};
this.displayCurrentMessage();
}
success(message: string, keepAfterNavigationChange = false, timeoutInSeconds: number = null) {
this.showMessage(message, 'success', keepAfterNavigationChange, timeoutInSeconds);
}
error(message: any, keepAfterNavigationChange = false, timeoutInSeconds: number = null) {
this.showMessage(message, 'error', keepAfterNavigationChange, timeoutInSeconds);
}
exception(message: string, error: any, keepAfterNavigationChange = false): void {
const errMsg = error.message || (error.json ? error.json().message : error);
this.error(message + ' \n' + errMsg, keepAfterNavigationChange);
......
......@@ -3,4 +3,7 @@ a:visited { text-decoration: none; }
a:hover { text-decoration: none; }
a:active { text-decoration: none; }
.form-control {
margin: 10px;
padding: 0;
}
<div id="page" class="login-page" [style]="'justify-content:center; align-items:center; height:100%'">
<div fxLayout="row" [style]="'justify-content:center; align-items:center; height:100%'">
<mat-card *ngIf="isUserAuthSSOEnabled() == true" fxFlex="400px" [style]="'width:400px;height:300px;margin:10px'">
<mat-card-title class="title-panel" >SSO Login: {{lookups.cachedApplicationInfo.ssoAuthenticationLabel}}</mat-card-title>
<mat-card *ngIf="isUserAuthSSOEnabled() == true" fxFlex="400px" [style]="'width:400px;height:240px;margin:10px'">
<mat-card-title class="title-panel">SSO Login: {{ lookups.cachedApplicationInfo.ssoAuthenticationLabel }}
</mat-card-title>
<mat-card-content style="align-items: center;justify-content: center;display: flex;height: 200px;">
<a mat-raised-button color="primary" href="{{lookups.cachedApplicationInfo.ssoAuthenticationURI}}"
[style]="'width:150px'">
......@@ -10,32 +11,62 @@
</a>
</mat-card-content>
</mat-card>
<mat-card *ngIf="isUserAuthPasswdEnabled() == true" fxFlex="400px" [style]="'width:400px;height:300px;margin:10px;'">
<mat-card *ngIf="isUserAuthPasswdEnabled() == true">
<mat-card-title class="title-panel">SMP Login</mat-card-title>
<mat-card-content style="align-items: center;justify-content: center;display: flex;flex-direction:column; height: 200px;">
<form name="loginForm" #loginForm="ngForm" (ngSubmit)="login()">
<mat-card-content style="width:380px; height: 260px;margin: 15px">
<mat-tab-group>
<mat-tab label="Login">
<ng-container *ngTemplateOutlet="loginFormContainer"></ng-container>
</mat-tab>
<mat-tab label="Request password reset">
<ng-container *ngTemplateOutlet="requestResetContainer"></ng-container>
</mat-tab>
</mat-tab-group>
</mat-card-content>
</mat-card>
</div>
<footer></footer>
</div>
<ng-template #loginFormContainer>
<div [formGroup]="loginForm" class="form-control">
<mat-form-field style="width: 100%">
<mat-label>Username</mat-label>
<input matInput placeholder="Username" name="username" [(ngModel)]="model.username"
#username="ngModel"
id="username_id"
<input matInput id="username_id"
formControlName="username"
auto-focus-directive
required>
</mat-form-field>
<mat-form-field style="width: 100%">
<mat-label>Password</mat-label>
<input type="password" matInput placeholder="Password" name="password" [(ngModel)]="model.password"
#password="ngModel" required id="password_id">
<input matInput id="password_id" type="password"
formControlName="password" required>
</mat-form-field>
<button mat-raised-button color="primary" [disabled]="!loginForm.form.valid" id="loginbutton_id"
[style]="'width:150px'">
<button mat-raised-button color="primary" id="loginbutton_id" [disabled]="!loginForm.valid"
(click)="login()"
[style]="'width:250px'">
<mat-icon>input</mat-icon>
<span> Login</span>
</button>
</form>
</mat-card-content>
</mat-card>
</div>
<footer></footer>
</ng-template>
<ng-template #requestResetContainer>
<div [formGroup]="resetForm" class="form-control">
<mat-form-field style="width: 100%">
<mat-label>Username</mat-label>
<input matInput id="reset_username_id"
formControlName="resetUsername"
auto-focus-directive
required>
</mat-form-field>
<button mat-raised-button color="primary" id="resetbutton_id" [disabled]="!resetForm.valid"
[style]="'width:250px'"
(click)="requestCredentialReset()"
>
<mat-icon>input</mat-icon>
<span> Request reset password</span>
</button>
</div>
</ng-template>
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Router, ActivatedRoute} from '@angular/router';
import {ActivatedRoute, Router} from '@angular/router';
import {SecurityService} from '../security/security.service';
import {AlertMessageService} from '../common/alert-message/alert-message.service';
import {SecurityEventService} from '../security/security-event.service';
......@@ -7,12 +7,15 @@ import {User} from '../security/user.model';
import {MatDialog} from '@angular/material/dialog';
import {DefaultPasswordDialogComponent} from 'app/security/default-password-dialog/default-password-dialog.component';
import {Subscription} from 'rxjs';
import {ExpiredPasswordDialogComponent} from '../common/dialogs/expired-password-dialog/expired-password-dialog.component';
import {
ExpiredPasswordDialogComponent
} from '../common/dialogs/expired-password-dialog/expired-password-dialog.component';
import {GlobalLookups} from "../common/global-lookups";
import {PasswordChangeDialogComponent} from "../common/dialogs/password-change-dialog/password-change-dialog.component";
import {InformationDialogComponent} from "../common/dialogs/information-dialog/information-dialog.component";
import {formatDate} from "@angular/common";
import {EntityStatus} from "../common/enums/entity-status.enum";
import {FormControl, FormGroup, Validators} from "@angular/forms";
@Component({
templateUrl: './login.component.html',
......@@ -20,7 +23,8 @@ import {EntityStatus} from "../common/enums/entity-status.enum";
})
export class LoginComponent implements OnInit, OnDestroy {
model: any = {};
loginForm: FormGroup;
resetForm: FormGroup;
loading = false;
returnUrl: string;
sub: Subscription;
......@@ -35,14 +39,21 @@ export class LoginComponent implements OnInit, OnDestroy {
private dialog: MatDialog) {
}
ngOnInit() {
this.initForm();
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
this.sub = this.securityEventService.onLoginSuccessEvent().subscribe(
user => {
if (user && user.passwordExpired) {
if (user.forceChangeExpiredPassword) {
this.dialog.open(PasswordChangeDialogComponent, {data: {user:user,adminUser:false}}).afterClosed().subscribe(res =>
this.dialog.open(PasswordChangeDialogComponent, {
data: {
user: user,
adminUser: false
}
}).afterClosed().subscribe(res =>
this.securityService.finalizeLogout(res)
);
} else {
......@@ -66,7 +77,7 @@ export class LoginComponent implements OnInit, OnDestroy {
switch (error.status) {
case HTTP_UNAUTHORIZED:
message = error.error.errorDescription;
this.model.password = '';
this.loginForm['password'].setValue('');
break;
case HTTP_FORBIDDEN:
const forbiddenCode = error.message;
......@@ -77,7 +88,7 @@ export class LoginComponent implements OnInit, OnDestroy {
default:
message = error.status + ' The username/password combination you provided are not valid. Please try again or contact your administrator.';
// clear the password
this.model.password = '';
this.loginForm['password'].setValue('');
break;
}
break;
......@@ -93,10 +104,39 @@ export class LoginComponent implements OnInit, OnDestroy {
});
}
private initForm() {
this.loginForm = new FormGroup({
username: new FormControl('', Validators.required),
password: new FormControl('', Validators.required),
});
this.resetForm = new FormGroup({
resetUsername: new FormControl('', Validators.required),
});
}
login() {
// clear alerts
this.alertService.clearAlert();
this.securityService.login(this.model.username, this.model.password);
if (this.loginForm.valid) {
this.securityService.login(
this.loginForm.get('username').value,
this.loginForm.get('password').value
);
} else {
this.alertService.error('Please enter a valid username and password.');
}
}
requestCredentialReset() {
this.alertService.clearAlert();
if (this.resetForm.valid) {
this.securityService.requestCredentialReset(
this.resetForm.get('resetUsername').value
);
} else {
this.alertService.error('Please enter a valid username.');
}
}
showWarningBeforeExpire(user: User) {
......@@ -108,25 +148,6 @@ export class LoginComponent implements OnInit, OnDestroy {
}).afterClosed().subscribe(() => this.router.navigate([this.returnUrl]));
}
verifyDefaultLoginUsed() {
const currentUser: User = this.securityService.getCurrentUser();
if (currentUser.defaultPasswordUsed) {
this.dialog.open(DefaultPasswordDialogComponent);
}
}
private convertWithMode(config) {
return (config && config.data)
? {
...config,
data: {
...config.data,
mode: config.data.mode || (config.data.edit ? EntityStatus.PERSISTED :EntityStatus.NEW)
}
}
: config;
}
ngOnDestroy(): void {
this.sub.unsubscribe();
}
......
a:link { text-decoration: none; }
a:visited { text-decoration: none; }
a:hover { text-decoration: none; }
a:active { text-decoration: none; }
.form-control {
margin: 10px;
padding: 0;
}
<div id="page" class="login-page" [style]="'justify-content:center; align-items:center; height:100%'">
<div fxLayout="row" [style]="'justify-content:center; align-items:center; height:100%'">
<div [formGroup]="resetForm" class="form-control">
<div style="display:flex;flex-direction: column;">
<mat-form-field style="width:100%">
<mat-label>Enter Username</mat-label>
<input matInput formControlName="resetUsername" id="reset_username_id" required auto-focus-directive>
</mat-form-field>
<mat-form-field style="width:100%">
<mat-label>New Password</mat-label>
<input matInput [type]="hideNewPwdFiled ? 'password' : 'text'"
formControlName="new-password" id="np_id" required>
<mat-icon matSuffix
(click)="hideNewPwdFiled = !hideNewPwdFiled">{{ hideNewPwdFiled ? 'visibility_off' : 'visibility' }}
</mat-icon>
<smp-field-error *ngIf="passwordError('new-password', 'error')">New password must not be equal than old
current
password!
</smp-field-error>
<smp-field-error *ngIf="passwordError('new-password', 'pattern')">{{ passwordValidationMessage }}
</smp-field-error>
</mat-form-field>
<mat-form-field style="width:100%">
<mat-label>Confirm New Password</mat-label>
<input matInput [type]="hideConfPwdFiled ? 'password' : 'text'"
formControlName="confirm-new-password" id="cnp_id" required>
<mat-icon matSuffix
(click)="hideConfPwdFiled = !hideConfPwdFiled">{{ hideConfPwdFiled ? 'visibility_off' : 'visibility' }}
</mat-icon>
<smp-field-error *ngIf="passwordError('confirm-new-password', 'error')">Confirm valued does not match new
password!
</smp-field-error>
</mat-form-field>
<div class="required-fields">* required fields</div>
</div>
<mat-toolbar-row>
<button id="changeCurrentUserPasswordButton" mat-raised-button color="primary"
(click)="resetPassword()"
[disabled]="!resetForm.valid ">
<mat-icon>check_circle</mat-icon>
<span>Set/change password</span>
</button>
<button id="closeDialogButton" mat-raised-button color="primary"
(click)="cancel()"
mat-dialog-close>
<mat-icon>cancel</mat-icon>
<span>Cancel</span>
</button>
</mat-toolbar-row>
</div>
</div>
<footer></footer>
</div>
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {FormGroup, UntypedFormControl, Validators} from "@angular/forms";
import {GlobalLookups} from "../../common/global-lookups";
import {equal} from "../../common/dialogs/password-change-dialog/password-change-dialog.component";
import {SecurityService} from "../security.service";
@Component({
templateUrl: './reset-credential.component.html',
styleUrls: ['./reset-credential.component.css']
})
export class ResetCredentialComponent implements OnInit {
resetForm: FormGroup;
resetToken: string
hideNewPwdFiled: boolean = true;
hideConfPwdFiled: boolean = true;
constructor(private router: Router,
private activatedRoute: ActivatedRoute,
private lookups: GlobalLookups,
private securityService: SecurityService) {
}
ngOnInit() {
this.initForm();
this.resetToken = this.activatedRoute.snapshot.params['resetToken'];
}
private initForm() {
let newPasswdFormControl: UntypedFormControl = new UntypedFormControl({value: null, readonly: false},
[Validators.required, Validators.pattern(this.passwordValidationRegExp)]);
let confirmNewPasswdFormControl: UntypedFormControl = new UntypedFormControl({value: null, readonly: false},
[Validators.required, equal(newPasswdFormControl, true)]);
this.resetForm = new FormGroup({
'resetUsername': new UntypedFormControl({value: null, readonly: true}, null),
'new-password': newPasswdFormControl,
'confirm-new-password': confirmNewPasswdFormControl
});
this.resetForm.controls['resetUsername'].setValue("");
this.resetForm.controls['new-password'].setValue("");
this.resetForm.controls['confirm-new-password'].setValue("");
}
public passwordError = (controlName: string, errorName: string) => {
return this.resetForm.controls[controlName].hasError(errorName);
}
get passwordValidationMessage() {
return this.lookups.cachedApplicationInfo?.passwordValidationRegExpMessage;
}
get passwordValidationRegExp() {
return this.lookups.cachedApplicationInfo?.passwordValidationRegExp;
}
public resetPassword() {
this.securityService.credentialReset(this.resetForm.controls['resetUsername'].value,
this.resetToken,
this.resetForm.controls['new-password'].value,);
}
public cancel() {
this.router.navigate(['/search']);
}
}
......@@ -33,12 +33,50 @@ export class SecurityService {
password: password
}),
{headers})
.subscribe((response: User) => {
.subscribe({
next: (response: User) => {
this.updateUserDetails(response);
this.securityEventService.notifyLoginSuccessEvent(response);
},
(error: any) => {
error: (error: any) => {
this.alertService.error(error.error?.errorDescription)
this.securityEventService.notifyLoginErrorEvent(error);
}
});
}
requestCredentialReset(userid: string) {
let headers: HttpHeaders = new HttpHeaders({'Content-Type': 'application/json'});
return this.http.post<User>(SmpConstants.REST_PUBLIC_SECURITY_RESET_CREDENTIALS_REQUEST,
JSON.stringify({
credentialName: userid,
credentialType: 'USERNAME_PASSWORD',
}),
{headers})
.subscribe({
complete: () => { }, // completeHandler
error: (error: any) => {this.alertService.error(error) }, // errorHandler
next: () => { this.alertService.success("A confirmation email has been sent to your registered email address for user ["+userid+"]. " +
"Please follow the instructions in the email to complete the account reset process. " +
"If you did not receive mail try later or contact administrator ", true, -1);
this.router.navigate(['/search']);}
});
}
credentialReset(userid: string, token: string, newPassword: string) {
let headers: HttpHeaders = new HttpHeaders({'Content-Type': 'application/json'});
return this.http.post<User>(SmpConstants.REST_PUBLIC_SECURITY_RESET_CREDENTIALS,
JSON.stringify({
credentialName: userid,
credentialValue: newPassword,
credentialType: 'USERNAME_PASSWORD',
resetToken: token,
}),
{headers})
.subscribe({
complete: () => { this.router.navigate(['/login']); }, // completeHandler
error: (error: any) => {this.alertService.error(error);this.router.navigate(['/login']); }, // errorHandler
next: () => { this.alertService.success("Password has been reset successfully. Please login with new password", true, -1);}
});
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment