Bläddra i källkod

Implement Change Password dialog on HelpDesk detail page - part 1 (random passwords dialog)

Joseph White 7 år sedan
förälder
incheckning
80083831e3

+ 11 - 3
client/src/helpdesk/helpdesk-detail.component.ts

@@ -30,8 +30,9 @@ import {IPeopleService} from '../services/people.service';
 import {IPerson} from '../models/person.model';
 import {IPerson} from '../models/person.model';
 import IasDialogComponent from '../ux/ias-dialog.component';
 import IasDialogComponent from '../ux/ias-dialog.component';
 
 
-let verificationsDialogTemplateUrl = require('./verifications-dialog.template.html');
 let helpdeskDetailDialogTemplateUrl = require('./helpdesk-detail-dialog.template.html');
 let helpdeskDetailDialogTemplateUrl = require('./helpdesk-detail-dialog.template.html');
+let passwordSuggestionsDialogTemplateUrl = require('./password-suggestions-dialog.html');
+let verificationsDialogTemplateUrl = require('./verifications-dialog.template.html');
 
 
 const STATUS_WAIT = 'wait';
 const STATUS_WAIT = 'wait';
 const STATUS_CONFIRM = 'confirm';
 const STATUS_CONFIRM = 'confirm';
@@ -84,7 +85,14 @@ export default class HelpDeskDetailComponent {
     }
     }
 
 
     changePassword(): void {
     changePassword(): void {
-        alert('Change password dialog');
+        this.IasDialogService
+            .open({
+                controller: 'PasswordSuggestionsDialogController as $ctrl',
+                templateUrl: passwordSuggestionsDialogTemplateUrl,
+                locals: {
+                    personUserKey: this.getUserKey(),
+                }
+            });
     }
     }
 
 
     clearOtpSecret(): void {
     clearOtpSecret(): void {
@@ -174,7 +182,7 @@ export default class HelpDeskDetailComponent {
                         $scope.confirm = () => {
                         $scope.confirm = () => {
                             $scope.status = STATUS_WAIT;
                             $scope.status = STATUS_WAIT;
                             helpDeskService.customAction(button.name, userKey).then((data: ISuccessResponse) => {
                             helpDeskService.customAction(button.name, userKey).then((data: ISuccessResponse) => {
-                                // TODO - error dialog? (btw this error dialog is slightly different)
+                                // TODO - error dialog? (note that this error dialog is slightly different)
                                 $scope.status = STATUS_SUCCESS;
                                 $scope.status = STATUS_SUCCESS;
                                 $scope.title = translateFilter('Title_Success');
                                 $scope.title = translateFilter('Title_Success');
                                 $scope.secondaryText = null;
                                 $scope.secondaryText = null;

+ 9 - 7
client/src/helpdesk/helpdesk.module.ts

@@ -22,16 +22,17 @@
 
 
 
 
 import { module } from 'angular';
 import { module } from 'angular';
-import HelpDeskSearchComponent from './helpdesk-search.component';
-import uxModule from '../ux/ux.module';
-import PersonCardComponent from '../peoplesearch/person-card.component';
-import PromiseService from '../services/promise.service';
+import { DateFilter } from './date.filters';
 import HelpDeskDetailComponent from './helpdesk-detail.component';
 import HelpDeskDetailComponent from './helpdesk-detail.component';
+import HelpDeskSearchComponent from './helpdesk-search.component';
 import LocalStorageService from '../services/local-storage.service';
 import LocalStorageService from '../services/local-storage.service';
-import VerificationsDialogController from './verifications-dialog.controller';
 import ObjectService from '../services/object.service';
 import ObjectService from '../services/object.service';
+import PasswordSuggestionsDialogController from './password-suggestions.controller';
+import PersonCardComponent from '../peoplesearch/person-card.component';
+import PromiseService from '../services/promise.service';
 import RecentVerificationsDialogController from './recent-verifications-dialog.controller';
 import RecentVerificationsDialogController from './recent-verifications-dialog.controller';
-import {DateFilter} from './date.filters';
+import uxModule from '../ux/ux.module';
+import VerificationsDialogController from './verifications-dialog.controller';
 
 
 require('../peoplesearch/peoplesearch.scss');
 require('../peoplesearch/peoplesearch.scss');
 
 
@@ -44,8 +45,9 @@ module(moduleName, [
     .component('helpDeskSearch', HelpDeskSearchComponent)
     .component('helpDeskSearch', HelpDeskSearchComponent)
     .component('helpDeskDetail', HelpDeskDetailComponent)
     .component('helpDeskDetail', HelpDeskDetailComponent)
     .component('personCard', PersonCardComponent)
     .component('personCard', PersonCardComponent)
-    .controller('VerificationsDialogController', VerificationsDialogController)
+    .controller('PasswordSuggestionsDialogController', PasswordSuggestionsDialogController)
     .controller('RecentVerificationsDialogController', RecentVerificationsDialogController)
     .controller('RecentVerificationsDialogController', RecentVerificationsDialogController)
+    .controller('VerificationsDialogController', VerificationsDialogController)
     .filter('dateFilter', DateFilter)
     .filter('dateFilter', DateFilter)
     .service('ObjectService', ObjectService)
     .service('ObjectService', ObjectService)
     .service('PromiseService', PromiseService)
     .service('PromiseService', PromiseService)

+ 53 - 0
client/src/helpdesk/password-suggestions-dialog.html

@@ -0,0 +1,53 @@
+<!--
+  ~ Password Management Servlets (PWM)
+  ~ http://www.pwm-project.org
+  ~
+  ~ Copyright (c) 2006-2009 Novell, Inc.
+  ~ Copyright (c) 2009-2017 The PWM Project
+  ~
+  ~ This program is free software; you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation; either version 2 of the License, or
+  ~ (at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program; if not, write to the Free Software
+  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+  -->
+
+<ias-dialog class="password-suggestions-dialog">
+    <div class="ias-dialog-header">
+        <div class="ias-title" ng-bind="'Title_RandomPasswords' | translate"></div>
+    </div>
+
+    <div class="ias-dialog-body">
+        <p ng-bind="'Display_PasswordGeneration' | translate"></p>
+        <table>
+            <tbody>
+            <tr ng-repeat="i in [0,2,4,6,8,10,12,14,16,18]">
+                <td ng-repeat="j in [i, i+1]">
+                    <div href="#" ng-bind="$ctrl.passwordSuggestions[j]" ng-click="$ctrl.onChoosePassword(j)">
+                    </div>
+                </td>
+            </tr>
+            </tbody>
+        </table>
+    </div>
+
+    <div class="ias-actions">
+        <mf-button ng-click="$ctrl.onMoreRandomsButtonClick()"
+                   ng-disabled="$ctrl.fetchingRandoms">{{ 'Button_More' | translate }}</mf-button>
+        <mf-button ng-click="close()">{{ 'Button_Cancel' | translate }}</mf-button>
+    </div>
+    <mf-icon-button class="ias-dialog-close-button"
+                    icon="close_thick"
+                    id="close-icon"
+                    ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
+                    ng-click="close()">
+    </mf-icon-button>
+</ias-dialog>

+ 15 - 0
client/src/helpdesk/password-suggestions-dialog.scss

@@ -0,0 +1,15 @@
+ias-dialog {
+  &.password-suggestions-dialog {
+    table {
+      td {
+        min-width: 160px;
+        height: 15px;
+
+        div {
+          cursor: pointer;
+          font-family: monospace;
+        }
+      }
+    }
+  }
+}

+ 82 - 0
client/src/helpdesk/password-suggestions.controller.ts

@@ -0,0 +1,82 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+import {IHelpDeskService, IRandomPasswordResponse} from '../services/helpdesk.service';
+import {IPromise, IQService} from 'angular';
+
+let RANDOM_MAPPING_LENGTH = 20;
+
+require('helpdesk/password-suggestions-dialog.scss');
+
+export default class PasswordSuggestionsDialogController {
+    fetchingRandoms: boolean;
+    passwordSuggestions: string[];
+
+    static $inject = [ '$q', 'HelpDeskService', 'personUserKey' ];
+    constructor(private $q: IQService, private HelpDeskService: IHelpDeskService, private personUserKey: string) {
+        this.passwordSuggestions = Array(20).fill('');
+        this.fetchRandoms();
+    }
+
+    onChoosePassword(index: number) {
+        let password = this.passwordSuggestions[index];
+        // change password
+    }
+
+    onMoreRandomsButtonClick() {
+        this.fetchRandoms();
+    }
+
+    fetchRandoms() {
+        this.fetchingRandoms = true;
+        let ordering = this.generateRandomMapping();
+        let promiseChain: IPromise<any> = this.$q.when();
+        ordering.forEach((index: number) => {
+            promiseChain = promiseChain.then(this.passwordSuggestionFactory(index));
+        });
+        promiseChain.then(() => {
+            this.fetchingRandoms = false;
+        });
+    }
+
+    generateRandomMapping(): number[] {
+        let map: number[] = [];
+        for (let i = 0; i < RANDOM_MAPPING_LENGTH; i++) {
+            map.push(i);
+        }
+        let randomComparatorFunction = () => 0.5 - Math.random();
+        map.sort(randomComparatorFunction);
+        map.sort(randomComparatorFunction);
+        return map;
+    }
+
+    passwordSuggestionFactory(index: number): any {
+        return () => {
+            return this.HelpDeskService.getRandomPassword(this.personUserKey).then(
+                (result: IRandomPasswordResponse) => {
+                    this.passwordSuggestions[index] = result.password;
+                }
+            );
+        };
+    }
+}

+ 3 - 0
client/src/i18n/translations_en.json

@@ -9,6 +9,7 @@
   "Button_Email": "Email",
   "Button_Email": "Email",
   "Button_GoBack": "Go Back",
   "Button_GoBack": "Go Back",
   "Button_HelpdeskClearOtpSecret": "Clear OTP Secret",
   "Button_HelpdeskClearOtpSecret": "Clear OTP Secret",
+  "Button_More": "More",
   "Button_OK": "OK",
   "Button_OK": "OK",
   "Button_OTP": "OTP",
   "Button_OTP": "OTP",
   "Button_SMS": "SMS",
   "Button_SMS": "SMS",
@@ -19,6 +20,7 @@
   "Confirm_DeleteUser": "Are you sure you wish to proceed?  If you continue, the selected user will be deleted permanently.  This can not be undone.",
   "Confirm_DeleteUser": "Are you sure you wish to proceed?  If you continue, the selected user will be deleted permanently.  This can not be undone.",
   "Display_CaptchaRefresh": "Refresh",
   "Display_CaptchaRefresh": "Refresh",
   "Display_HelpdeskOtpValidation": "Instruct the user to load their mobile authentication app and share the current pass code.",
   "Display_HelpdeskOtpValidation": "Instruct the user to load their mobile authentication app and share the current pass code.",
+  "Display_PasswordGeneration": "The following passwords have been randomly generated for you.  These passwords are based on real words to make them easier to remember, but have been modified to make them difficult to guess.",
   "Display_PleaseWait": "Loading...",
   "Display_PleaseWait": "Loading...",
   "Display_SearchResultsExceeded": "Search results exceeded maximum search size",
   "Display_SearchResultsExceeded": "Search results exceeded maximum search size",
   "Display_SearchResultsNone": "No results",
   "Display_SearchResultsNone": "No results",
@@ -43,6 +45,7 @@
   "Title_PeopleSearch": "People Search",
   "Title_PeopleSearch": "People Search",
   "Title_PeopleSearchCard": "People Search Cards",
   "Title_PeopleSearchCard": "People Search Cards",
   "Title_PeopleSearchTable": "People Search Table",
   "Title_PeopleSearchTable": "People Search Table",
+  "Title_RandomPasswords": "Random Passwords",
   "Title_RecentVerifications": "Recent Verifications",
   "Title_RecentVerifications": "Recent Verifications",
   "Title_SecurityResponses": "Security Responses",
   "Title_SecurityResponses": "Security Responses",
   "Title_Status": "Status",
   "Title_Status": "Status",

+ 8 - 0
client/src/services/helpdesk-config.service.dev.ts

@@ -50,6 +50,10 @@ export default class HelpDeskConfigService extends ConfigBaseService implements
         });
         });
     }
     }
 
 
+    getPasswordUiMode(): IPromise<string> {
+        return this.$q.resolve('both');
+    }
+
     getColumnConfig(): IPromise<any> {
     getColumnConfig(): IPromise<any> {
         return this.$q.resolve({
         return this.$q.resolve({
             givenName: 'First Name',
             givenName: 'First Name',
@@ -79,6 +83,10 @@ export default class HelpDeskConfigService extends ConfigBaseService implements
         ]);
         ]);
     }
     }
 
 
+    maskPasswordsEnabled(): IPromise<boolean> {
+        return this.$q.resolve(true);
+    }
+
     verificationsEnabled(): IPromise<boolean> {
     verificationsEnabled(): IPromise<boolean> {
         return this.$q.resolve(true);
         return this.$q.resolve(true);
     }
     }

+ 13 - 1
client/src/services/helpdesk-config.service.ts

@@ -28,12 +28,14 @@ import {ConfigBaseService, IConfigService} from './base-config.service';
 
 
 const ACTION_BUTTONS_CONFIG = 'actions';
 const ACTION_BUTTONS_CONFIG = 'actions';
 const COLUMN_CONFIG = 'helpdesk_search_columns';
 const COLUMN_CONFIG = 'helpdesk_search_columns';
-const VERIFICATION_METHODS_CONFIG = 'verificationMethods';
+const MASK_PASSWORDS_CONFIG = 'helpdesk_setting_maskPasswords';
+const PASSWORD_UI_MODE_CONFIG = 'helpdesk_setting_PwUiMode';
 const TOKEN_SEND_METHOD_CONFIG = 'helpdesk_setting_tokenSendMethod';
 const TOKEN_SEND_METHOD_CONFIG = 'helpdesk_setting_tokenSendMethod';
 const TOKEN_VERIFICATION_METHOD = 'TOKEN';
 const TOKEN_VERIFICATION_METHOD = 'TOKEN';
 const TOKEN_SMS_ONLY = 'SMSONLY';
 const TOKEN_SMS_ONLY = 'SMSONLY';
 const TOKEN_EMAIL_ONLY = 'EMAILONLY';
 const TOKEN_EMAIL_ONLY = 'EMAILONLY';
 const VERIFICATION_FORM_CONFIG = 'verificationForm';
 const VERIFICATION_FORM_CONFIG = 'verificationForm';
+const VERIFICATION_METHODS_CONFIG = 'verificationMethods';
 export const TOKEN_CHOICE = 'CHOICE_SMS_EMAIL';
 export const TOKEN_CHOICE = 'CHOICE_SMS_EMAIL';
 
 
 export const VERIFICATION_METHOD_NAMES = {
 export const VERIFICATION_METHOD_NAMES = {
@@ -63,9 +65,11 @@ export type IVerificationMap = {name: string, label: string}[];
 
 
 export interface IHelpDeskConfigService extends IConfigService {
 export interface IHelpDeskConfigService extends IConfigService {
     getActionButtons(): IPromise<IActionButtons>;
     getActionButtons(): IPromise<IActionButtons>;
+    getPasswordUiMode(): IPromise<string>;
     getTokenSendMethod(): IPromise<string>;
     getTokenSendMethod(): IPromise<string>;
     getVerificationAttributes(): IPromise<IVerificationMap>;
     getVerificationAttributes(): IPromise<IVerificationMap>;
     getVerificationMethods(): IPromise<IVerificationMap>;
     getVerificationMethods(): IPromise<IVerificationMap>;
+    maskPasswordsEnabled(): IPromise<boolean>;
     verificationsEnabled(): IPromise<boolean>;
     verificationsEnabled(): IPromise<boolean>;
 }
 }
 
 
@@ -84,6 +88,10 @@ export default class HelpDeskConfigService extends ConfigBaseService implements
         return this.getValue(COLUMN_CONFIG);
         return this.getValue(COLUMN_CONFIG);
     }
     }
 
 
+    getPasswordUiMode(): IPromise<string> {
+        return this.getValue(PASSWORD_UI_MODE_CONFIG);
+    }
+
     getTokenSendMethod(): IPromise<string> {
     getTokenSendMethod(): IPromise<string> {
         return this.getValue(TOKEN_SEND_METHOD_CONFIG);
         return this.getValue(TOKEN_SEND_METHOD_CONFIG);
     }
     }
@@ -129,6 +137,10 @@ export default class HelpDeskConfigService extends ConfigBaseService implements
         });
         });
     }
     }
 
 
+    maskPasswordsEnabled(): IPromise<boolean> {
+        return this.getValue(MASK_PASSWORDS_CONFIG);
+    }
+
     verificationsEnabled(): IPromise<boolean> {
     verificationsEnabled(): IPromise<boolean> {
         return this.getValue(VERIFICATION_METHODS_CONFIG)
         return this.getValue(VERIFICATION_METHODS_CONFIG)
             .then((result: IVerificationResponse) => {
             .then((result: IVerificationResponse) => {

+ 11 - 1
client/src/services/helpdesk.service.dev.ts

@@ -21,7 +21,7 @@
  */
  */
 
 
 import {
 import {
-    IHelpDeskService, IRecentVerifications, ISuccessResponse, IVerificationStatus,
+    IHelpDeskService, IRandomPasswordResponse, IRecentVerifications, ISuccessResponse, IVerificationStatus,
     IVerificationTokenResponse
     IVerificationTokenResponse
 } from './helpdesk.service';
 } from './helpdesk.service';
 import {IPromise, IQService, ITimeoutService, IWindowService} from 'angular';
 import {IPromise, IQService, ITimeoutService, IWindowService} from 'angular';
@@ -413,6 +413,12 @@ export default class HelpDeskService implements IHelpDeskService {
         });
         });
     }
     }
 
 
+    getRandomPassword(userKey: string): IPromise<IRandomPasswordResponse> {
+        let randomNumber = Math.floor(Math.random() * Math.floor(100));
+        let passwordSuggestion = 'suggestion' + randomNumber;
+        return this.simulateResponse({ password: passwordSuggestion });
+    }
+
     getRecentVerifications(): IPromise<IRecentVerifications> {
     getRecentVerifications(): IPromise<IRecentVerifications> {
         return this.simulateResponse([
         return this.simulateResponse([
             {
             {
@@ -467,4 +473,8 @@ export default class HelpDeskService implements IHelpDeskService {
     validateVerificationData(userKey: string, data: any, method: string): IPromise<IVerificationStatus> {
     validateVerificationData(userKey: string, data: any, method: string): IPromise<IVerificationStatus> {
         return this.simulateResponse({ passed: true });
         return this.simulateResponse({ passed: true });
     }
     }
+
+    get showStrengthMeter(): boolean {
+        return false;
+    }
 }
 }

+ 29 - 0
client/src/services/helpdesk.service.ts

@@ -33,6 +33,8 @@ const VERIFICATION_PROCESS_ACTIONS = {
     OTP: 'validateOtpCode'
     OTP: 'validateOtpCode'
 };
 };
 
 
+const DEFAULT_SHOW_STRENGTH_METER = false;
+
 export interface IHelpDeskService {
 export interface IHelpDeskService {
     checkVerification(userKey: string): IPromise<IVerificationStatus>;
     checkVerification(userKey: string): IPromise<IVerificationStatus>;
     clearOtpSecret(userKey: string): IPromise<ISuccessResponse>;
     clearOtpSecret(userKey: string): IPromise<ISuccessResponse>;
@@ -40,10 +42,12 @@ export interface IHelpDeskService {
     customAction(actionName: string, userKey: string): IPromise<ISuccessResponse>;
     customAction(actionName: string, userKey: string): IPromise<ISuccessResponse>;
     deleteUser(userKey: string): IPromise<ISuccessResponse>;
     deleteUser(userKey: string): IPromise<ISuccessResponse>;
     getPerson(userKey: string): IPromise<any>;
     getPerson(userKey: string): IPromise<any>;
+    getRandomPassword(userKey: string): IPromise<IRandomPasswordResponse>;
     getRecentVerifications(): IPromise<IRecentVerifications>;
     getRecentVerifications(): IPromise<IRecentVerifications>;
     sendVerificationToken(userKey: string, choice: string): IPromise<IVerificationTokenResponse>;
     sendVerificationToken(userKey: string, choice: string): IPromise<IVerificationTokenResponse>;
     unlockIntruder(userKey: string): IPromise<ISuccessResponse>;
     unlockIntruder(userKey: string): IPromise<ISuccessResponse>;
     validateVerificationData(userKey: string, formData: any, tokenData: any): IPromise<IVerificationStatus>;
     validateVerificationData(userKey: string, formData: any, tokenData: any): IPromise<IVerificationStatus>;
+    showStrengthMeter: boolean;
 }
 }
 
 
 export interface IButtonInfo {
 export interface IButtonInfo {
@@ -61,6 +65,10 @@ type IRecentVerification = {
     method: string
     method: string
 }
 }
 
 
+export interface IRandomPasswordResponse {
+    password: string;
+}
+
 export interface ISuccessResponse {
 export interface ISuccessResponse {
     successMessage: string;
     successMessage: string;
 }
 }
@@ -167,6 +175,19 @@ export default class HelpDeskService implements IHelpDeskService {
             });
             });
     }
     }
 
 
+    getRandomPassword(userKey: string): IPromise<IRandomPasswordResponse> {
+        let url: string = this.pwmService.getServerUrl('randomPassword');
+        let data = {
+            username: userKey,
+            strength: 0
+        };
+        return this.pwmService
+            .httpRequest(url, { data: data })
+            .then((result: IRandomPasswordResponse) => {
+                return this.$q.resolve(result);
+            });
+    }
+
     getRecentVerifications(): IPromise<IRecentVerifications> {
     getRecentVerifications(): IPromise<IRecentVerifications> {
         let url: string = this.pwmService.getServerUrl('showVerifications');
         let url: string = this.pwmService.getServerUrl('showVerifications');
         let data = {
         let data = {
@@ -224,4 +245,12 @@ export default class HelpDeskService implements IHelpDeskService {
                 return this.$q.resolve(result);
                 return this.$q.resolve(result);
             });
             });
     }
     }
+
+    get showStrengthMeter(): boolean {
+        if (this.PWM_GLOBAL) {
+            return this.PWM_GLOBAL['setting-showStrengthMeter'] || DEFAULT_SHOW_STRENGTH_METER;
+        }
+
+        return DEFAULT_SHOW_STRENGTH_METER;
+    }
 }
 }

+ 22 - 0
npm-debug.log

@@ -0,0 +1,22 @@
+0 info it worked if it ends with ok
+1 verbose cli [ 'C:\\Program Files\\nodejs\\node.exe',
+1 verbose cli   'C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js',
+1 verbose cli   'start' ]
+2 info using npm@3.10.10
+3 info using node@v6.10.1
+4 verbose stack Error: ENOENT: no such file or directory, open 'D:\deps-dx\pwm\pwm\package.json'
+4 verbose stack     at Error (native)
+5 verbose cwd D:\deps-dx\pwm\pwm
+6 error Windows_NT 10.0.14393
+7 error argv "C:\\Program Files\\nodejs\\node.exe" "C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js" "start"
+8 error node v6.10.1
+9 error npm  v3.10.10
+10 error path D:\deps-dx\pwm\pwm\package.json
+11 error code ENOENT
+12 error errno -4058
+13 error syscall open
+14 error enoent ENOENT: no such file or directory, open 'D:\deps-dx\pwm\pwm\package.json'
+15 error enoent ENOENT: no such file or directory, open 'D:\deps-dx\pwm\pwm\package.json'
+15 error enoent This is most likely not a problem with npm itself
+15 error enoent and is related to npm not being able to find a file.
+16 verbose exit [ -4058, true ]

+ 1 - 0
server/src/main/webapp/WEB-INF/jsp/helpdesk.jsp

@@ -50,6 +50,7 @@
 <jsp:include page="/WEB-INF/jsp/fragment/footer.jsp"/>
 <jsp:include page="/WEB-INF/jsp/fragment/footer.jsp"/>
 <link rel="stylesheet" type="text/css" href="<pwm:url url='/public/resources/webjars/pwm-client/fonts.css' addContext="true"/>"/>
 <link rel="stylesheet" type="text/css" href="<pwm:url url='/public/resources/webjars/pwm-client/fonts.css' addContext="true"/>"/>
 <pwm:script-ref url="/public/resources/webjars/pwm-client/helpdesk.ng.js" />
 <pwm:script-ref url="/public/resources/webjars/pwm-client/helpdesk.ng.js" />
+<pwm:script-ref url="/public/resources/js/changepassword.js"/>
 
 
 </body>
 </body>
 </html>
 </html>