diff --git a/apps/maarch_entreprise/login.php b/apps/maarch_entreprise/login.php index fbc17474c61ccc86c543ac8c1dfa5c14af6db4ed..690967c457fb00879ce75681a43359640d4b3e5f 100755 --- a/apps/maarch_entreprise/login.php +++ b/apps/maarch_entreprise/login.php @@ -169,6 +169,11 @@ if (isset($_SESSION['info']) && $_SESSION['info'] != '') { echo '</script>'; } $loginObj->execute_login_script($loginMethods); +if ($_GET['update-password-token']) { + echo "<script>"; + echo "triggerAngular('#/update-password?token=". $_REQUEST['update-password-token']."')"; + echo "</script>"; +} echo '</div>'; diff --git a/rest/index.php b/rest/index.php index 187f0a9da0d74de8991c06fad61d73e768a1e6e4..86f81e447acbef8e7dcdd189b92b9ee5af334cfe 100755 --- a/rest/index.php +++ b/rest/index.php @@ -30,7 +30,7 @@ $app = new \Slim\App(['settings' => ['displayErrorDetails' => true, 'determineRo //Authentication $app->add(function (\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next) { - $routesWithoutAuthentication = ['GET/jnlp/{jnlpUniqueId}', 'POST/password', 'PUT/password', 'GET/initialize']; + $routesWithoutAuthentication = ['GET/jnlp/{jnlpUniqueId}', 'POST/password', 'PUT/password', 'GET/initialize', 'GET/passwordRules']; $route = $request->getAttribute('route'); $currentMethod = empty($route) ? '' : $route->getMethods()[0]; $currentRoute = empty($route) ? '' : $route->getPattern(); diff --git a/src/app/user/controllers/UserController.php b/src/app/user/controllers/UserController.php index 6bb023af58cb8400c7ac2edc1d1e9e77657606ce..27d4a1aef4c5bdd812c6f330211fdeccb580fd05 100755 --- a/src/app/user/controllers/UserController.php +++ b/src/app/user/controllers/UserController.php @@ -199,7 +199,7 @@ class UserController $resetToken = AuthenticationController::getResetJWT(['id' => $newUser['id'], 'expirationTime' => 1209600]); // 14 days UserModel::update(['set' => ['reset_token' => $resetToken], 'where' => ['id = ?'], 'data' => [$newUser['id']]]); - $url = UrlController::getCoreUrl() . '#/update-password?token=' . $resetToken . '&creation=true'; + $url = UrlController::getCoreUrl() . 'apps/maarch_entreprise/index.php?display=true&page=login&update-password-token=' . $resetToken; EmailController::createEmail([ 'userId' => $newUser['id'], 'data' => [ @@ -1535,7 +1535,7 @@ class UserController $resetToken = AuthenticationController::getResetJWT(['id' => $user['id'], 'expirationTime' => 3600]); // 1 hour UserModel::update(['set' => ['reset_token' => $resetToken], 'where' => ['id = ?'], 'data' => [$user['id']]]); - $url = UrlController::getCoreUrl() . '#/update-password?token=' . $resetToken; + $url = UrlController::getCoreUrl() . 'apps/maarch_entreprise/index.php?display=true&page=login&update-password-token=' . $resetToken; EmailController::createEmail([ 'userId' => $user['id'], 'data' => [ diff --git a/src/frontend/app/app-routing.module.ts b/src/frontend/app/app-routing.module.ts index 979d8b46ab843766477fdf19babda9ad57711247..41268b5ec65e1333facfcff2070b6f6609311cac 100755 --- a/src/frontend/app/app-routing.module.ts +++ b/src/frontend/app/app-routing.module.ts @@ -14,6 +14,7 @@ import { AppGuard, AfterProcessGuard } from '../service/app.guard'; import { FolderDocumentListComponent } from './folder/document-list/folder-document-list.component'; import { IndexationComponent } from './indexation/indexation.component'; import { ForgotPasswordComponent } from './login/forgotPassword/forgotPassword.component'; +import { UpdatePasswordComponent } from './login/updatePassword/updatePassword.component'; import { ProcessComponent } from './process/process.component'; @NgModule({ @@ -32,6 +33,7 @@ import { ProcessComponent } from './process/process.component'; { path: 'signatureBook/users/:userId/groups/:groupId/baskets/:basketId/resources/:resId', canActivate: [AppGuard],component: SignatureBookComponent }, { path: 'indexing/:groupId', canActivate: [AppGuard],component: IndexationComponent }, { path: 'forgot-password', component: ForgotPasswordComponent }, + { path: 'update-password', component: UpdatePasswordComponent }, { path: '**', redirectTo: 'home', pathMatch: 'full' }, ], { useHash: true }), ], diff --git a/src/frontend/app/app.module.ts b/src/frontend/app/app.module.ts index 6f3b00a06f0fc3c0d981aacbf4ddb523323b0a64..06ba5ae0b43cbda4812c2f8cd72156d19dcc37f5 100755 --- a/src/frontend/app/app.module.ts +++ b/src/frontend/app/app.module.ts @@ -68,6 +68,7 @@ import { PrintSeparatorComponent } from './separator/prin import { IndexationComponent } from './indexation/indexation.component'; import { ForgotPasswordComponent } from './login/forgotPassword/forgotPassword.component'; +import { UpdatePasswordComponent } from './login/updatePassword/updatePassword.component'; import { HistoryWorkflowResumeComponent } from './history/history-workflow-resume/history-workflow-resume.component'; import { NoteResumeComponent } from './notes/note-resume/note-resume.component'; import { AttachmentShowModalComponent } from './attachments/attachment-show-modal/attachment-show-modal.component'; @@ -142,6 +143,7 @@ import { ActionsService } from './actions/actions.service'; FolderActionListComponent, IndexationComponent, ForgotPasswordComponent, + UpdatePasswordComponent, HistoryWorkflowResumeComponent, NoteResumeComponent, AttachmentsResumeComponent, diff --git a/src/frontend/app/login/updatePassword/updatePassword.component.html b/src/frontend/app/login/updatePassword/updatePassword.component.html new file mode 100644 index 0000000000000000000000000000000000000000..43c5533254da1ee4d823a61ebf4b1a4e75413ea0 --- /dev/null +++ b/src/frontend/app/login/updatePassword/updatePassword.component.html @@ -0,0 +1,33 @@ +<div class="login-content"> + <mat-icon svgIcon="maarchLogo" class="maarchLogo"></mat-icon> + <mat-card class="login-form"> + <form (ngSubmit)="updatePassword()"> + <div class="alert-message alert-message-info" role="alert"> + {{lang.logInOncePasswordChanged}} + </div> + <mat-form-field class="input-row"> + <input name="newPassword" matInput [(ngModel)]="password.newPassword" + placeholder="{{lang.typeNewPassword}}" [type]="hideNewPassword ? 'password' : 'text'" + (keyup)="checkPasswordValidity(password.newPassword)"> + <mat-icon matSuffix (click)="hideNewPassword = !hideNewPassword" class="fa fa-2x" + [ngClass]="[hideNewPassword ? 'fa-eye-slash' : 'fa-eye']"> + </mat-icon> + <mat-hint style="color:red;">{{handlePassword.errorMsg}}</mat-hint> + </mat-form-field> + <mat-form-field class="input-row"> + <input name="passwordConfirmation" matInput [(ngModel)]="password.passwordConfirmation" + placeholder="{{lang.retypeNewPassword}}" + [type]="hideNewPasswordConfirm ? 'password' : 'text'"> + <mat-icon matSuffix (click)="hideNewPasswordConfirm = !hideNewPasswordConfirm" class="fa fa-2x" + [ngClass]="[hideNewPasswordConfirm ? 'fa-eye-slash' : 'fa-eye']"></mat-icon> + <mat-hint style="color:red;" *ngIf="password.passwordConfirmation !== password.newPassword"> + {{lang.passwordNotMatch}}</mat-hint> + <mat-hint style="color:green;" + *ngIf="password.passwordConfirmation === password.newPassword && password.newPassword.length > 0 && password.passwordConfirmation.length> 0"> + {{lang.samePassword}}</mat-hint> + </mat-form-field> + <button type="submit" mat-button [disabled]="allowValidate() || loading">{{labelButton}}</button> + <button type="button" mat-button (click)="cancel();">{{lang.cancel}}</button> + </form> + </mat-card> +</div> \ No newline at end of file diff --git a/src/frontend/app/login/updatePassword/updatePassword.component.scss b/src/frontend/app/login/updatePassword/updatePassword.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..d93a4fc62f3e0ec75fd470c422bca0a6cdcf9cb2 --- /dev/null +++ b/src/frontend/app/login/updatePassword/updatePassword.component.scss @@ -0,0 +1,39 @@ +@import '../../../css/vars.scss'; + +.login-content { + background-color: $primary; + height: 100vh; + width: 100vw; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.login-form { + max-width: 480px; + text-align: center; +} + +.input-row { + width: 100%; + padding-top: 10px; + padding-bottom: 10px; +} + +.maarchLogo { + position: absolute; + transition: all 1s ease-in-out; + width: 250px; + height: auto; + padding-bottom: 10px; + transform: translateY(-230px); +} + +footer { + color: white; + position: absolute; + bottom: 5px; + font-size: 10px; + opacity: 0.5; +} diff --git a/src/frontend/app/login/updatePassword/updatePassword.component.ts b/src/frontend/app/login/updatePassword/updatePassword.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..ac9a9ec1ccaca041dfb3e06f35badc7c380fa7b3 --- /dev/null +++ b/src/frontend/app/login/updatePassword/updatePassword.component.ts @@ -0,0 +1,173 @@ +import { Component, OnInit } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import { MatIconRegistry } from '@angular/material'; +import { HttpClient } from '@angular/common/http'; +import { Router, ActivatedRoute } from '@angular/router'; +import { NotificationService } from '../../notification.service'; +import { LANG } from '../../translate.component'; +import { finalize } from 'rxjs/operators'; + +declare function $j(selector: any) : any; + +@Component({ + templateUrl: 'updatePassword.component.html', + styleUrls: ['updatePassword.component.scss'], +}) +export class UpdatePasswordComponent implements OnInit { + + lang: any = LANG; + loadingForm: boolean = false; + loading: boolean = false; + + token: string = ''; + + password: any = { + newPassword: '', + passwordConfirmation: '' + }; + labelButton: string = this.lang.update; + + hideNewPassword: Boolean = true; + hideNewPasswordConfirm: Boolean = true; + + // HANDLE PASSWORD + passwordRules: any = { + minLength: { enabled: false, value: 0 }, + complexityUpper: { enabled: false, value: 0 }, + complexityNumber: { enabled: false, value: 0 }, + complexitySpecial: { enabled: false, value: 0 }, + renewal: { enabled: false, value: 0 }, + historyLastUse: { enabled: false, value: 0 }, + }; + + handlePassword: any = { + error: false, + errorMsg: '' + }; + + ruleText = ''; + otherRuleText = ''; + + + constructor(private router: Router, private route: ActivatedRoute, public http: HttpClient, iconReg: MatIconRegistry, sanitizer: DomSanitizer, public notificationService: NotificationService) { + iconReg.addSvgIcon('maarchLogo', sanitizer.bypassSecurityTrustResourceUrl('static.php?filename=logo_white.svg')); + this.route.queryParams + .subscribe(params => { + this.token = params.token; + }); + $j('main-header').remove(); + } + + ngOnInit(): void { + this.getPassRules(); + } + + updatePassword() { + this.labelButton = this.lang.sending; + this.loading = true; + + this.http.put('../../rest/password', { 'token': this.token, 'password': this.password.newPassword }) + .pipe( + finalize(() => { + this.labelButton = this.lang.update; + this.loading = false; + }) + ) + .subscribe((data: any) => { + this.loadingForm = true; + this.notificationService.success(this.lang.passwordChanged); + location.href = 'index.php?display=true&page=login'; + }, (err: any) => { + this.notificationService.handleErrors(err); + }); + } + + checkPasswordValidity(password: string) { + this.handlePassword.error = true; + + if (!password.match(/[A-Z]/g) && this.passwordRules.complexityUpper.enabled) { + this.handlePassword.errorMsg = this.lang.passwordcomplexityUpperRequired; + } else if (!password.match(/[0-9]/g) && this.passwordRules.complexityNumber.enabled) { + this.handlePassword.errorMsg = this.lang.passwordcomplexityNumberRequired; + } else if (!password.match(/[^A-Za-z0-9]/g) && this.passwordRules.complexitySpecial.enabled) { + this.handlePassword.errorMsg = this.lang.passwordcomplexitySpecialRequired; + } else if (password.length < this.passwordRules.minLength.value && this.passwordRules.minLength.enabled) { + this.handlePassword.errorMsg = this.passwordRules.minLength.value + ' ' + this.lang.passwordminLength + ' !'; + } else { + this.handlePassword.error = false; + this.handlePassword.errorMsg = ''; + } + } + + getPassRules() { + this.handlePassword.error = false; + this.handlePassword.errorMsg = ''; + + this.http.get('../../rest/passwordRules') + .subscribe((data: any) => { + const ruleTextArr: String[] = []; + const otherRuleTextArr: String[] = []; + + data.rules.forEach((rule: any) => { + if (rule.label === 'minLength') { + this.passwordRules.minLength.enabled = rule.enabled; + this.passwordRules.minLength.value = rule.value; + if (rule.enabled) { + ruleTextArr.push(rule.value + ' ' + this.lang['password' + rule.label]); + + } + + } else if (rule.label === 'complexityUpper') { + this.passwordRules.complexityUpper.enabled = rule.enabled; + this.passwordRules.complexityUpper.value = rule.value; + if (rule.enabled) { + ruleTextArr.push(this.lang['password' + rule.label]); + } + + } else if (rule.label === 'complexityNumber') { + this.passwordRules.complexityNumber.enabled = rule.enabled; + this.passwordRules.complexityNumber.value = rule.value; + if (rule.enabled) { + ruleTextArr.push(this.lang['password' + rule.label]); + } + + } else if (rule.label === 'complexitySpecial') { + this.passwordRules.complexitySpecial.enabled = rule.enabled; + this.passwordRules.complexitySpecial.value = rule.value; + if (rule.enabled) { + ruleTextArr.push(this.lang['password' + rule.label]); + } + } else if (rule.label === 'renewal') { + this.passwordRules.renewal.enabled = rule.enabled; + this.passwordRules.renewal.value = rule.value; + if (rule.enabled) { + otherRuleTextArr.push(this.lang['password' + rule.label] + ' <b>' + rule.value + ' ' + this.lang.days + '</b>. ' + this.lang['password2' + rule.label]+'.'); + + } + } else if (rule.label === 'historyLastUse') { + this.passwordRules.historyLastUse.enabled = rule.enabled; + this.passwordRules.historyLastUse.value = rule.value; + if (rule.enabled) { + otherRuleTextArr.push(this.lang['passwordhistoryLastUseDesc'] + ' <b>' + rule.value + '</b> ' + this.lang['passwordhistoryLastUseDesc2']+'.'); + } + } + }); + this.ruleText = ruleTextArr.join(', '); + this.otherRuleText = otherRuleTextArr.join('<br/>'); + }, (err) => { + this.notificationService.handleErrors(err); + }); + } + + allowValidate() { + if ((this.handlePassword.error || this.password.newPassword !== this.password.passwordConfirmation || this.password.newPassword.length === 0 || this.password.passwordConfirmation.length === 0)) { + return true; + } else { + return false; + } + } + + cancel() { + location.href = 'index.php?display=true&page=login'; + } +} diff --git a/src/frontend/lang/lang-en.ts b/src/frontend/lang/lang-en.ts index 5898bb7ef3ff51c8c9da8b42b0329e0aea7ba1ae..9a62651d730fc5e59ffcb97d53ca9ad5ab9a9816 100755 --- a/src/frontend/lang/lang-en.ts +++ b/src/frontend/lang/lang-en.ts @@ -1277,4 +1277,7 @@ export const LANG_EN = { "send" : "Send", "generation" : "Generation...", "requestSentByEmail" : "The request has been sent to you by email.", + "logInOncePasswordChanged": "You will be asked to log in once the password has been changed.", + "samePassword" : "Passwords match", + "sending" : "Sending...", }; diff --git a/src/frontend/lang/lang-fr.ts b/src/frontend/lang/lang-fr.ts index b17a6b1978c2f43523422ba4436c773ddb334b04..51a0e1cf11b28f05f25d1d3469328812c7ed4914 100755 --- a/src/frontend/lang/lang-fr.ts +++ b/src/frontend/lang/lang-fr.ts @@ -1314,4 +1314,7 @@ export const LANG_FR = { "send" : "Envoyer", "generation" : "Génération...", "requestSentByEmail" : "La demande vous a été envoyé par courriel.", + "logInOncePasswordChanged": "Vous serez amené à vous connecter une fois la modification du mot de passe effectuée.", + "samePassword" : "Les mots de passe sont identiques", + "sending" : "Envoi...", }; diff --git a/src/frontend/lang/lang-nl.ts b/src/frontend/lang/lang-nl.ts index ec1568f7f68a4ad2b7bf9062a493d544341fa0e9..7d5cfcad8115dcc105235320fd1b18079106f099 100755 --- a/src/frontend/lang/lang-nl.ts +++ b/src/frontend/lang/lang-nl.ts @@ -1302,4 +1302,7 @@ export const LANG_NL = { "send" : "Send", //_TO_TRANSLATE "generation" : "Generation...", //_TO_TRANSLATE "requestSentByEmail" : "The request has been sent to you by email.", //_TO_TRANSLATE + "logInOncePasswordChanged": "You will be asked to log in once the password has been changed.", //_TO_TRANSLATE + "samePassword" : "Passwords match", //_TO_TRANSLATE + "sending" : "Sending...", //_TO_TRANSLATE };