diff --git a/config/package-solution.json b/config/package-solution.json index 3d82c43c1..7013a8f75 100644 --- a/config/package-solution.json +++ b/config/package-solution.json @@ -3,7 +3,7 @@ "solution": { "name": "SPFx app dev Simple Password Vault", "id": "d091b369-f63d-459c-846e-5a4323e31745", - "version": "1.0.0.0", + "version": "1.1.0.0", "includeClientSideAssets": true, "skipFeatureDeployment": true, "isDomainIsolated": false, diff --git a/package.json b/package.json index b99405615..85db57838 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sp-fx-app-dev-simple-password-vault", - "version": "1.0.0", + "version": "1.1.0", "private": true, "main": "lib/index.js", "engines": { diff --git a/src/models/IVaultData.ts b/src/models/IVaultData.ts index 816ee6a20..52f740bef 100644 --- a/src/models/IVaultData.ts +++ b/src/models/IVaultData.ts @@ -1,6 +1,3 @@ export interface IVaultData { masterPW: string; - // username: string; - // password: string; - // note: string; } \ No newline at end of file diff --git a/src/services/PasswordVaultService.ts b/src/services/PasswordVaultService.ts index 1bfc38342..d8388883f 100644 --- a/src/services/PasswordVaultService.ts +++ b/src/services/PasswordVaultService.ts @@ -1,7 +1,6 @@ import * as CryptoJS from 'crypto-js'; import { isNullOrEmpty, toBoolean } from '@spfxappdev/utility'; import { SessionStorage } from '@spfxappdev/storage'; -import { IVaultData } from '@src/models/IVaultData'; import { ModuleType } from '@src/models'; @@ -71,7 +70,7 @@ export class PasswordVaultService implements IPasswordVaultService { this.cache = new SessionStorage(cacheSettings); this.isVaultOpen = toBoolean(this.cache.get(this.encryptedInstanceId)); - let pwFromCache = this.cache.get(this.encryptedMasterPWInstanceId); + const pwFromCache = this.cache.get(this.encryptedMasterPWInstanceId); this.encryptedMasterPw = isNullOrEmpty(pwFromCache) ? "" : pwFromCache; if(!isNullOrEmpty(this.encryptedMasterPw)) { @@ -103,7 +102,7 @@ export class PasswordVaultService implements IPasswordVaultService { public open(masterPW: string, encryptedMasterPW: string): boolean { const masterPWEncrypted = CryptoJS.HmacSHA256(masterPW, this.masterSecretKey); - if(masterPWEncrypted.toString() == encryptedMasterPW) { + if(masterPWEncrypted.toString() === encryptedMasterPW) { this.isVaultOpen = true; this.cache.set(this.encryptedInstanceId, true); this.encryptedMasterPw = this.encrypt(masterPW, this.masterSecretKey); @@ -115,7 +114,6 @@ export class PasswordVaultService implements IPasswordVaultService { return false; } - public isOpen(): boolean { return this.isVaultOpen; } diff --git a/src/webparts/passwordVault/PasswordVaultWebPart.manifest.json b/src/webparts/passwordVault/PasswordVaultWebPart.manifest.json index 004a60de0..4c0deb9c1 100644 --- a/src/webparts/passwordVault/PasswordVaultWebPart.manifest.json +++ b/src/webparts/passwordVault/PasswordVaultWebPart.manifest.json @@ -22,9 +22,18 @@ "officeFabricIconFontName": "ProtectRestrict", "properties": { "masterPW": "", - "username": "", - "password": "", - "note": "" + "modules": [ + { + "id": "718def44-3e0c-48bf-8843-ccabefece27e", + "data": "", + "type": "UserField" + }, + { + "id": "65025036-e345-43f7-9561-6d3026294b2e", + "data": "", + "type": "PasswordField" + }, + ] } }] } diff --git a/src/webparts/passwordVault/PasswordVaultWebPart.ts b/src/webparts/passwordVault/PasswordVaultWebPart.ts index a17599f6c..3af0470c6 100644 --- a/src/webparts/passwordVault/PasswordVaultWebPart.ts +++ b/src/webparts/passwordVault/PasswordVaultWebPart.ts @@ -39,15 +39,15 @@ export default class PasswordVaultWebPart extends SPFxAppDevClientSideWebPart { this.onTitleChanged(title); }, onVaultDataChanged: (encryptedMasterPw: string, modules: IModule[]): void => { this.onVaultDataChanged(encryptedMasterPw, modules); + }, + onVaultPasswordChanged: (encryptedMasterPw: string): void => { + this.onMasterPasswordChanged(encryptedMasterPw); } } ); @@ -66,7 +66,7 @@ export default class PasswordVaultWebPart extends SPFxAppDevClientSideWebPart { @@ -26,10 +28,10 @@ export interface IPasswordVaultProps extends ISPFxAppDevWebPartComponentProps { @@ -43,7 +45,8 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent = {}; private decryptedModuleData: Record = {}; @@ -152,26 +157,17 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent - - {strings.DontLoseMasterpasswordLabel} - + + {this.renderCommandBarButtons()} + {this.renderChangePasswordDialog()} + + {this.isNewVault && + + {strings.DontLoseMasterpasswordLabel} + + }
-
-
- { - this.enteredMasterPW = newValue; - this.setState({ - isSaveButtonDisabled: this.isSaveButtonDisabled() - }); - }} - /> -
-
+ {this.isNewVault && this.renderMasterPasswordControls()} {this.state.modules.map((module: IModule, index: number): JSX.Element => { return this.renderModuleEditMode(module, index); @@ -187,31 +183,7 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent
{ - this.isNewVault = false; - let encryptedMaster = this.currentMasterPW; - - if(!this.enteredMasterPW.IsEmpty()) { - encryptedMaster = this.props.passwordVaultService.setMasterPassword(this.enteredMasterPW); - this.currentMasterPW = encryptedMaster; - } - - const encryptedModules: IModule[] = []; - this.state.modules.forEach((module: IModule) => { - const encryptedValue: string = this.props.passwordVaultService.encryptModuleData(module.type, this.decryptedModuleData[module.id]); - - encryptedModules.push({ - id: module.id, - data: encryptedValue, - type: module.type - }); - }); - - this.props.onVaultDataChanged(encryptedMaster, encryptedModules); - - this.setState({ - modules: encryptedModules - }); - + this.onSaveButtonClick(); }}> {strings.SaveLabel} @@ -274,19 +246,19 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent - {module.type == ModuleType.UserField && + {module.type === ModuleType.UserField && { this.decryptedModuleData[module.id] = newVal; }} isDisplayMode={false} /> } - {module.type == ModuleType.PasswordField && + {module.type === ModuleType.PasswordField && { this.decryptedModuleData[module.id] = newVal; }} isDisplayMode={false} /> } - {module.type == ModuleType.NoteField && + {module.type === ModuleType.NoteField && { this.decryptedModuleData[module.id] = newVal; @@ -307,15 +279,15 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent
- {module.type == ModuleType.UserField && + {module.type === ModuleType.UserField && } - {module.type == ModuleType.PasswordField && + {module.type === ModuleType.PasswordField && } - {module.type == ModuleType.NoteField && + {module.type === ModuleType.NoteField && }
@@ -350,7 +322,7 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent) => { - if(ev.keyCode == 13) { + if(ev.keyCode === 13) { this.onOpenVault(); } }} @@ -375,9 +347,123 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent); } + private renderMasterPasswordControls(): JSX.Element { + return ( +
+
+ { + this.enteredMasterPW = newValue; + this.setState({ + isSaveButtonDisabled: this.isSaveButtonDisabled() + }); + }} + /> +
+ +
+ { + this.repeatedEnteredMasterPW = newValue; + this.setState({ + isSaveButtonDisabled: this.isSaveButtonDisabled() + }); + }} + /> +
+
); + } + + private renderChangePasswordDialog(): JSX.Element { + return ( + + ); + } + + private renderCommandBarButtons(): JSX.Element { + const buttons: ICommandBarItemProps[] = []; + + const saveButton: ICommandBarItemProps = { + key: 'saveSettings', + text: strings.SaveLabel, + disabled: this.isSaveButtonDisabled(), + iconProps: { iconName: 'Save' }, + onClick: () => { + this.onSaveButtonClick(); + } + } + + buttons.push(saveButton); + + if(!this.isNewVault) { + const changeMasterPwButton: ICommandBarItemProps = { + key: 'changeMasterPassword', + text: strings.ChangeMasterPasswordButtonText, + iconProps: { iconName: 'PasswordField' }, + onClick: () => { + this.toggleChangePasswordDialogVisibility(); + }, + } + + buttons.push(changeMasterPwButton); + } + + if(!this.helper.functions.isNullOrEmpty(this.currentMasterPW)) { + const closeButton: ICommandBarItemProps = { + key: 'closeVault', + text: strings.CloseVaultLabel, + iconProps: { iconName: 'Lock' }, + onClick: () => { + this.closeVault(); + } + } + + buttons.push(closeButton); + } + + return ( + + ); + } private isSaveButtonDisabled(): boolean { - if(this.isNewVault && this.helper.functions.isNullOrEmpty(this.enteredMasterPW)) { + if((this.isNewVault || this.state.showChangePasswordDialog) && this.helper.functions.isNullOrEmpty(this.enteredMasterPW)) { + return true; + } + + if(!this.enteredMasterPW.Equals(this.repeatedEnteredMasterPW, false)) { return true; } @@ -404,8 +490,6 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent { + const encryptedValue: string = this.props.passwordVaultService.encryptModuleData(module.type, this.decryptedModuleData[module.id]); + + encryptedModules.push({ + id: module.id, + data: encryptedValue, + type: module.type + }); + }); + + this.props.onVaultDataChanged(encryptedMaster, encryptedModules); + + this.setState({ + modules: encryptedModules + }); + + this.enteredMasterPW = ''; + this.repeatedEnteredMasterPW = ''; + } + + private onChangePasswordClick(): void { + let encryptedMaster = this.currentMasterPW; + + if(!this.enteredMasterPW.IsEmpty()) { + encryptedMaster = this.props.passwordVaultService.setMasterPassword(this.enteredMasterPW); + this.currentMasterPW = encryptedMaster; + this.props.onVaultPasswordChanged(encryptedMaster); + } + + this.enteredMasterPW = ''; + this.repeatedEnteredMasterPW = ''; + this.toggleChangePasswordDialogVisibility(); + } + + private toggleChangePasswordDialogVisibility(): void { + this.setState({ + showChangePasswordDialog: !this.state.showChangePasswordDialog, + isSaveButtonDisabled: !this.state.showChangePasswordDialog + }); + } } \ No newline at end of file diff --git a/src/webparts/passwordVault/components/UserField.tsx b/src/webparts/passwordVault/components/UserField.tsx index 3cbd10dc8..1f2d72c02 100644 --- a/src/webparts/passwordVault/components/UserField.tsx +++ b/src/webparts/passwordVault/components/UserField.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; -import styles from './PasswordVault.module.scss'; -import { ActionButton, Callout, Icon, TooltipHost, DirectionalHint, TextField, ITextField } from 'office-ui-fabric-react'; +import { Callout, Icon, DirectionalHint, TextField, ITextField } from 'office-ui-fabric-react'; import * as strings from 'PasswordVaultWebPartStrings'; import { getDeepOrDefault, issetDeep, isset } from '@spfxappdev/utility'; diff --git a/src/webparts/passwordVault/loc/de-de.js b/src/webparts/passwordVault/loc/de-de.js index 835bfbc1a..8fb2c808c 100644 --- a/src/webparts/passwordVault/loc/de-de.js +++ b/src/webparts/passwordVault/loc/de-de.js @@ -5,6 +5,7 @@ define([], function() { "PasswordLabel": "Passwort", "MasterPasswordLabel": "Master Passwort", "SetMasterPasswordLabel": "Master Passwort", + "RepeatMasterPasswordLabel": "Master Passwort wiederholen", "ChangeMasterPasswordLabel": "Master Passwort ändern", "UsernameLabel": "Benutezrname", "NoteLabel": "Notiz", @@ -22,5 +23,8 @@ define([], function() { "PasswordModuleLabel": "Passwort", "UsernameModuleLabel": "Benutzername", "NoteModuleLabel": "Notiz", + "ChangeMasterPasswordButtonText": "Master Passwort ändern", + "ChangeMasterPasswordDialogTitle": "Master Passwort ändern", + "CancelLabel": "Abbrechen" } }); \ No newline at end of file diff --git a/src/webparts/passwordVault/loc/en-us.js b/src/webparts/passwordVault/loc/en-us.js index 7903e31e0..2eb6e4847 100644 --- a/src/webparts/passwordVault/loc/en-us.js +++ b/src/webparts/passwordVault/loc/en-us.js @@ -5,6 +5,7 @@ define([], function() { "PasswordLabel": "Password", "MasterPasswordLabel": "Master Password", "SetMasterPasswordLabel": "Master Password", + "RepeatMasterPasswordLabel": "Repeat Master Passwort", "ChangeMasterPasswordLabel": "Change Master Password", "UsernameLabel": "Username", "NoteLabel": "Note", @@ -22,5 +23,8 @@ define([], function() { "PasswordModuleLabel": "Password", "UsernameModuleLabel": "Username", "NoteModuleLabel": "Note", + "ChangeMasterPasswordButtonText": "Change Master Passwort", + "ChangeMasterPasswordDialogTitle": "Change Master Passwort", + "CancelLabel": "Cancel" } }); \ No newline at end of file diff --git a/src/webparts/passwordVault/loc/mystrings.d.ts b/src/webparts/passwordVault/loc/mystrings.d.ts index 843fa8801..d2946b46d 100644 --- a/src/webparts/passwordVault/loc/mystrings.d.ts +++ b/src/webparts/passwordVault/loc/mystrings.d.ts @@ -4,6 +4,7 @@ declare interface IPasswordVaultWebPartStrings { PasswordLabel: string; MasterPasswordLabel: string; SetMasterPasswordLabel: string; + RepeatMasterPasswordLabel: string; ChangeMasterPasswordLabel: string; UsernameLabel: string; NoteLabel: string; @@ -21,6 +22,9 @@ declare interface IPasswordVaultWebPartStrings { PasswordModuleLabel: string; UsernameModuleLabel: string; NoteModuleLabel: string; + ChangeMasterPasswordButtonText: string; + ChangeMasterPasswordDialogTitle: string; + CancelLabel: string; } declare module 'PasswordVaultWebPartStrings' {