Finilization v1.1.0.0

This commit is contained in:
Sergej Schwabauer 2023-04-13 19:29:30 +02:00
parent 2b3da349b0
commit e10ba53c1a
14 changed files with 260 additions and 87 deletions

View File

@ -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,

View File

@ -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": {

View File

@ -1,6 +1,3 @@
export interface IVaultData {
masterPW: string;
// username: string;
// password: string;
// note: string;
}

View File

@ -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;
}

View File

@ -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"
},
]
}
}]
}

View File

@ -39,15 +39,15 @@ export default class PasswordVaultWebPart extends SPFxAppDevClientSideWebPart<IP
Title: this.properties.Title,
passwordVaultService: this.passwordVaultService,
masterPW: this.properties.masterPW,
// username: this.properties.username,
// password: this.properties.password,
// note: this.properties.note,
modules: this.properties.modules||[],
onTitleChanged: (title: string): void => {
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<IP
});
}
if (!this.helper.functions.isNullOrEmpty(oldProps.username)) {
if (!this.helper.functions.isNullOrEmpty(oldProps.password)) {
this.properties.modules.push({
id: Guid.newGuid().toString(),
type: ModuleType.PasswordField,
@ -81,6 +81,23 @@ export default class PasswordVaultWebPart extends SPFxAppDevClientSideWebPart<IP
data: oldProps.note
});
}
this.removePropertiesFromOldVersion();
}
private removePropertiesFromOldVersion(): void {
const oldProps: any = this.properties as any;
if (this.helper.functions.isset(oldProps.username)) {
delete oldProps.username;
}
if (this.helper.functions.isset(oldProps.password)) {
delete oldProps.password;
}
if (this.helper.functions.isset(oldProps.note)) {
delete oldProps.note;
}
}
public getLogCategory(): string {
@ -94,9 +111,11 @@ export default class PasswordVaultWebPart extends SPFxAppDevClientSideWebPart<IP
public onVaultDataChanged(encryptedMasterPW: string, modules: IModule[]): void {
this.properties.masterPW = encryptedMasterPW;
this.properties.modules = modules;
// this.properties.note = vaultData.note;
// this.properties.password = vaultData.password;
// this.properties.username = vaultData.username;
this.removePropertiesFromOldVersion();
}
private onMasterPasswordChanged(encryptedMasterPW: string): void {
this.properties.masterPW = encryptedMasterPW;
}
protected onDispose(): void {

View File

@ -1,5 +1,4 @@
import * as React from 'react';
import styles from './PasswordVault.module.scss';
import * as strings from 'PasswordVaultWebPartStrings';
import { Label } from 'office-ui-fabric-react';
import { RichText } from "@pnp/spfx-controls-react/lib/RichText";

View File

@ -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';
@ -30,8 +29,6 @@ export default class PasswordField extends React.Component<IPasswordFieldProps,
}
return this.renderEditMode();
}
public renderDisplayMode(): JSX.Element {

View File

@ -34,6 +34,7 @@
&::before {
border-top-style: solid;
border-top-width: 2px;
border-top-color: $ms-color-neutralTertiary;
content: "";
height: 0;
position: absolute;
@ -49,6 +50,7 @@
border-radius: 50%;
border-style: solid;
border-width: 2px;
border-color: $ms-color-neutralTertiary;
box-sizing: border-box;
height: 24px;
line-height: 12px;
@ -57,7 +59,7 @@
position: relative;
transition: opacity .3s ease-in-out;
width: 24px;
background-color: #000;
background-color: $ms-color-neutralTertiary;
display: block;
@ -104,13 +106,19 @@
padding: 10px;
}
}
:global {
.ms-CommandBar {
padding: 0 0 10px;
}
}
}
:global {
.ql-editor
{
padding: 0px !important;
padding: 8px 0 0 !important;
}
.ql-editor[contenteditable='true'] {

View File

@ -3,7 +3,7 @@ import styles from './PasswordVault.module.scss';
import { SPFxAppDevWebPartComponent, ISPFxAppDevWebPartComponentProps } from '@spfxappdev/framework';
import PasswordVaultWebPart from '../PasswordVaultWebPart';
import { IPasswordVaultService } from '@src/services/PasswordVaultService';
import { DefaultButton, IconButton, MessageBar, MessageBarType, PrimaryButton, TextField } from 'office-ui-fabric-react';
import { Dialog, DefaultButton, IconButton, MessageBar, MessageBarType, PrimaryButton, TextField, CommandBar, ICommandBarItemProps, DialogFooter, DialogType } from 'office-ui-fabric-react';
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import * as strings from 'PasswordVaultWebPartStrings';
import AddNewModule from './AddNewModule';
@ -13,12 +13,14 @@ import UserField from './UserField';
import PasswordField from './PasswordField';
import NoteField from './NoteField';
import { cloneDeep } from '@microsoft/sp-lodash-subset';
import '@spfxappdev/utility/lib/extensions/ArrayExtensions';
interface IPasswordVaultState {
isVaultOpen: boolean;
showWrongMasterInfo: boolean;
isSaveButtonDisabled: boolean;
modules: IModule[];
showChangePasswordDialog: boolean;
}
export interface IPasswordVaultProps extends ISPFxAppDevWebPartComponentProps<PasswordVaultWebPart> {
@ -26,10 +28,10 @@ export interface IPasswordVaultProps extends ISPFxAppDevWebPartComponentProps<Pa
masterPW?: string;
onTitleChanged?(value: string): void;
onVaultDataChanged?(encryptedMaster: string, modules: IModule[]): void;
onVaultPasswordChanged?(encryptedMaster: string): void;
modules?: IModule[];
}
//TODO:
// "Change Password" as command button, only if vault already have set a master password
export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVaultWebPart, IPasswordVaultProps, IPasswordVaultState> {
@ -43,7 +45,8 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
isVaultOpen: this.isVaultOpen,
showWrongMasterInfo: false,
isSaveButtonDisabled: this.helper.functions.isNullOrEmpty(this.props.masterPW),
modules: cloneDeep(this.props.modules)
modules: cloneDeep(this.props.modules),
showChangePasswordDialog: false
};
private get isVaultOpen(): boolean {
@ -61,6 +64,8 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
private enteredMasterPW: string = "";
private repeatedEnteredMasterPW: string = "";
private encryptedModuleData: Record<string, string> = {};
private decryptedModuleData: Record<string, string> = {};
@ -152,26 +157,17 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
{showForm &&
<>
{this.renderCommandBarButtons()}
{this.renderChangePasswordDialog()}
{this.isNewVault &&
<MessageBar messageBarType={MessageBarType.warning}>
{strings.DontLoseMasterpasswordLabel}
</MessageBar>
}
<div className='spfxappdev-grid'>
<div className="spfxappdev-grid-row">
<div className="spfxappdev-grid-col spfxappdev-sm12">
<TextField
label={this.isNewVault ? strings.SetMasterPasswordLabel : strings.ChangeMasterPasswordLabel}
type="password"
required={this.isNewVault}
canRevealPassword={true}
onChange={(ev: any, newValue: string) => {
this.enteredMasterPW = newValue;
this.setState({
isSaveButtonDisabled: this.isSaveButtonDisabled()
});
}}
/>
</div>
</div>
{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<PasswordVa
<div className="spfxappdev-grid-row grid-footer">
<div className="spfxappdev-grid-col spfxappdev-sm12">
<PrimaryButton disabled={this.state.isSaveButtonDisabled} onClick={() => {
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}
</PrimaryButton>
@ -274,19 +246,19 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
<div className={styles["edit-container--content"]}>
{module.type == ModuleType.UserField &&
{module.type === ModuleType.UserField &&
<UserField defaultValue={this.decryptedModuleData[module.id]} tabIndex={index} onChange={(newVal: string) => {
this.decryptedModuleData[module.id] = newVal;
}} isDisplayMode={false} />
}
{module.type == ModuleType.PasswordField &&
{module.type === ModuleType.PasswordField &&
<PasswordField defaultValue={this.decryptedModuleData[module.id]} tabIndex={index} onChange={(newVal: string) => {
this.decryptedModuleData[module.id] = newVal;
}} isDisplayMode={false} />
}
{module.type == ModuleType.NoteField &&
{module.type === ModuleType.NoteField &&
<NoteField defaultValue={this.decryptedModuleData[module.id]} onChange={(newVal: string) => {
this.decryptedModuleData[module.id] = newVal;
@ -307,15 +279,15 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
<>
<div className="spfxappdev-grid-row" key={module.id}>
<div className="spfxappdev-grid-col spfxappdev-sm12">
{module.type == ModuleType.UserField &&
{module.type === ModuleType.UserField &&
<UserField defaultValue={this.decryptedModuleData[module.id]} isDisplayMode={true} tabIndex={index} />
}
{module.type == ModuleType.PasswordField &&
{module.type === ModuleType.PasswordField &&
<PasswordField defaultValue={this.decryptedModuleData[module.id]} isDisplayMode={true} tabIndex={index} />
}
{module.type == ModuleType.NoteField &&
{module.type === ModuleType.NoteField &&
<NoteField defaultValue={this.decryptedModuleData[module.id]} isDisplayMode={true} />
}
</div>
@ -350,7 +322,7 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
this.enteredMasterPW = newValue;
}}
onKeyUp={(ev: React.KeyboardEvent<HTMLInputElement>) => {
if(ev.keyCode == 13) {
if(ev.keyCode === 13) {
this.onOpenVault();
}
}}
@ -375,9 +347,123 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
</>);
}
private renderMasterPasswordControls(): JSX.Element {
return (
<div className="spfxappdev-grid-row">
<div className="spfxappdev-grid-col spfxappdev-sm12">
<TextField
label={this.isNewVault ? strings.SetMasterPasswordLabel : strings.ChangeMasterPasswordLabel}
type="password"
required={this.isNewVault}
canRevealPassword={true}
onChange={(ev: any, newValue: string) => {
this.enteredMasterPW = newValue;
this.setState({
isSaveButtonDisabled: this.isSaveButtonDisabled()
});
}}
/>
</div>
<div className="spfxappdev-grid-col spfxappdev-sm12">
<TextField
label={strings.RepeatMasterPasswordLabel}
type="password"
required={this.isNewVault}
canRevealPassword={true}
onChange={(ev: any, newValue: string) => {
this.repeatedEnteredMasterPW = newValue;
this.setState({
isSaveButtonDisabled: this.isSaveButtonDisabled()
});
}}
/>
</div>
</div>);
}
private renderChangePasswordDialog(): JSX.Element {
return (
<Dialog
hidden={!this.state.showChangePasswordDialog}
dialogContentProps={{
title: strings.ChangeMasterPasswordDialogTitle,
type: DialogType.normal,
}}
onDismiss={() => { this.toggleChangePasswordDialogVisibility(); }}
>
<MessageBar messageBarType={MessageBarType.warning}>
{strings.DontLoseMasterpasswordLabel}
</MessageBar>
{this.renderMasterPasswordControls()}
<DialogFooter>
<PrimaryButton
disabled={this.state.isSaveButtonDisabled}
onClick={() => { this.onChangePasswordClick(); }}
text={strings.SaveLabel} />
<DefaultButton onClick={() => { this.toggleChangePasswordDialogVisibility(); }} text={strings.CancelLabel} />
</DialogFooter>
</Dialog>
);
}
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 (
<CommandBar
items={buttons}
/>
);
}
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<PasswordVa
});
}
console.log("SSC onOpen", this.decryptedModuleData);
this.setState({
isVaultOpen: isCorrectPW,
showWrongMasterInfo: !isCorrectPW
@ -461,4 +545,55 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
modules: this.state.modules
});
}
private onSaveButtonClick(): void {
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.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
});
}
}

View File

@ -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';

View File

@ -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"
}
});

View File

@ -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"
}
});

View File

@ -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' {