Bugfix PW Changed + Open on Enter + Clipboardtext

This commit is contained in:
Sergej Schwabauer 2022-03-22 08:46:47 +01:00
parent 8a8435eda9
commit 20f68a60b8
7 changed files with 128 additions and 37 deletions

View File

@ -1,20 +1,20 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "sp-fx-app-dev-simple-password-vault-client-side-solution",
"name": "SPFx app dev Simple Password Vault",
"id": "d091b369-f63d-459c-846e-5a4323e31745",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": false,
"developer": {
"name": "",
"websiteUrl": "",
"name": "SPFXAppDev",
"websiteUrl": "https://spfx-app.dev/",
"privacyUrl": "",
"termsOfUseUrl": "",
"mpnId": ""
}
},
"paths": {
"zippedPackage": "solution/sp-fx-app-dev-simple-password-vault.sppkg"
"zippedPackage": "solution/spfxappdev-simple-password-vault.sppkg"
}
}

View File

@ -5,12 +5,6 @@ import { IVaultData } from '@src/interfaces/IVaultData';
export interface IPasswordVaultService {
// encryptPassword(password: string): string;
// decryptPassword(encryptedPassword: string): string;
// encryptUsername(username: string): string;
// decryptUsername(encryptedUsername: string): string;
// encryptDescription(description: string): string;
// decryptDescription(encryptedDescription: string): string;
encryptData(plainData: IVaultData): IVaultData;
decryptData(encryptedData: IVaultData): IVaultData;
open(masterPW: string, encryptedMasterPW: string): boolean;
@ -30,11 +24,17 @@ export class PasswordVaultService implements IPasswordVaultService {
private encryptedInstanceId: string = "";
private encryptedMasterPWInstanceId: string = "";
private secretWasSet: boolean = false;
private isVaultOpen: boolean = false;
private plainMasterPW: string = "";
private get plainMasterPW() : string {
return this.decrypt(this.encryptedMasterPw, this.masterSecretKey);
}
private encryptedMasterPw: string = "";
private hashedMasterPw: string = "";
@ -57,20 +57,21 @@ export class PasswordVaultService implements IPasswordVaultService {
constructor(instanceId: string) {
this.instanceId = instanceId;
this.encryptedInstanceId = this.encrypt(instanceId, instanceId);
this.encryptedInstanceId = CryptoJS.HmacSHA256(instanceId, instanceId).toString();
this.setSecret(`SPFxAppDevSecret_${instanceId}`);
this.encryptedMasterPWInstanceId = CryptoJS.HmacSHA256(`${instanceId}_Master`, this.masterSecretKey).toString();
this.cache = new SessionStorage(
{
const cacheSettings = {
...SessionStorage.DefaultSettings,
...{
DefaultTimeToLife: 5
}
});
};
this.cache = new SessionStorage(cacheSettings);
this.isVaultOpen = toBoolean(this.cache.get(this.encryptedInstanceId));
let pwFromCache = this.cache.get(this.encryptedMasterPWInstanceId);
this.encryptedMasterPw = isNullOrEmpty(pwFromCache) ? "" : pwFromCache;
}
public encryptData(plainData: Omit<IVaultData, "masterPW">): IVaultData {
@ -106,8 +107,9 @@ export class PasswordVaultService implements IPasswordVaultService {
if(masterPWEncrypted.toString() == encryptedMasterPW) {
this.isVaultOpen = true;
// this.cache.set(this.encryptedInstanceId, true);
this.plainMasterPW = masterPW;
this.cache.set(this.encryptedInstanceId, true);
this.encryptedMasterPw = this.encrypt(masterPW, this.masterSecretKey);
this.cache.set(this.encryptedMasterPWInstanceId, this.encryptedMasterPw);
this.hashedMasterPw = masterPWEncrypted.toString();
return true;
}
@ -122,13 +124,15 @@ export class PasswordVaultService implements IPasswordVaultService {
public close(): void {
this.isVaultOpen = false;
// this.cache.set(this.encryptedInstanceId, false);
this.plainMasterPW = "";
this.cache.set(this.encryptedInstanceId, false);
this.cache.remove(this.encryptedMasterPWInstanceId);
this.encryptedMasterPw = "";
}
public setMasterPassword(plainPassword: string): string {
this.plainMasterPW = plainPassword;
this.encryptedMasterPw = this.encrypt(plainPassword, this.masterSecretKey);
this.hashedMasterPw = CryptoJS.HmacSHA256(plainPassword, this.masterSecretKey).toString();
this.cache.set(this.encryptedMasterPWInstanceId, this.encryptedMasterPw);
return this.hashedMasterPw;
}
@ -175,7 +179,7 @@ export class PasswordVaultService implements IPasswordVaultService {
}
private encryptNote(note: string): string {
return this.encrypt(note, this.noteSecretKey)
return this.encrypt(note, this.noteSecretKey);
}
private decryptNote(encryptedNote: string): string {

View File

@ -129,4 +129,14 @@
}
}
.clipboard-callout {
margin-left: 20px;
padding: 10px;
background: $ms-color-themePrimary;
.ms-Callout-main {
background: $ms-color-themePrimary;
}
}
}

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, Icon, Label, MessageBar, MessageBarType, PrimaryButton, TextField } from 'office-ui-fabric-react';
import { Callout, DefaultButton, DirectionalHint, Icon, ITextField, Label, MessageBar, MessageBarType, PrimaryButton, TextField } from 'office-ui-fabric-react';
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import { RichText } from "@pnp/spfx-controls-react/lib/RichText";
import { IVaultData } from '@src/interfaces/IVaultData';
@ -13,6 +13,8 @@ interface IPasswordVaultState {
isVaultOpen: boolean;
showWrongMasterInfo: boolean;
isSaveButtonDisabled: boolean;
isCopyPasswordToClipboardCalloutHidden: boolean;
isCopyUsernameToClipboardCalloutHidden: boolean;
}
export interface IPasswordVaultProps extends ISPFxAppDevWebPartComponentProps<PasswordVaultWebPart> {
@ -36,7 +38,9 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
public state: IPasswordVaultState = {
isVaultOpen: this.isVaultOpen,
showWrongMasterInfo: false,
isSaveButtonDisabled: this.helper.functions.isNullOrEmpty(this.props.masterPW)
isSaveButtonDisabled: this.helper.functions.isNullOrEmpty(this.props.masterPW),
isCopyPasswordToClipboardCalloutHidden: true,
isCopyUsernameToClipboardCalloutHidden: true
};
private get isVaultOpen(): boolean {
@ -66,6 +70,12 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
private editModeUsername: string;
private currentMasterPW: string = "";
private usernameTextFieldDomElement: HTMLInputElement = null;
private passwordTextFieldDomElement: HTMLInputElement = null;
constructor(props: IPasswordVaultProps) {
super(props);
this.encryptedData = {
@ -75,10 +85,22 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
note: props.note
};
this.currentMasterPW = props.masterPW;
this.decryptedData = this.props.passwordVaultService.decryptData(this.encryptedData);
this.editModeNote = this.helper.functions.getDeepOrDefault(this.decryptedData, "note", "");
this.editModePw = this.helper.functions.getDeepOrDefault(this.decryptedData, "password", "");
this.editModeUsername = this.helper.functions.getDeepOrDefault(this.decryptedData, "username", "");
this.isNewVault = this.helper.functions.isNullOrEmpty(props.masterPW);
}
public componentDidUpdate(prevProps: Readonly<IPasswordVaultProps>, prevState: Readonly<IPasswordVaultState>, snapshot?: any): void {
if(prevProps.masterPW !== this.props.masterPW) {
this.currentMasterPW = this.props.masterPW;
}
}
public render(): React.ReactElement<IPasswordVaultProps> {
return (
<div className={styles.passwordVault}>
@ -99,12 +121,11 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
}
private renderDisplayMode(): JSX.Element {
const props: IPasswordVaultProps = this.props;
const showCopyToClipboard: boolean = this.helper.functions.issetDeep(window, "navigator.clipboard.writeText");
return (
<>
{this.helper.functions.isNullOrEmpty(props.masterPW) &&
{this.helper.functions.isNullOrEmpty(this.currentMasterPW) &&
<MessageBar messageBarType={MessageBarType.info}>
{strings.NoMasterPasswordSetLabel}
</MessageBar>
@ -121,15 +142,29 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
label={strings.UsernameLabel}
disabled={true}
defaultValue={this.decryptedData.username}
componentRef={(input: ITextField) => {
this.usernameTextFieldDomElement = this.helper.functions.getDeepOrDefault(input, "_textElement.current", null);
}}
onRenderSuffix={() => {
if(!showCopyToClipboard) {
return <></>;
}
return <Icon iconName={"Copy"} className="copy-icon" onClick={() => { this.copyToClipboard(this.decryptedData.username); }} />
return (<Icon iconName={"Copy"} className="copy-icon" onClick={() => { this.copyToClipboard(this.decryptedData.username, false); }} />);
}}
/>
{showCopyToClipboard && this.helper.functions.isset(this.usernameTextFieldDomElement) &&
<Callout
hidden={this.state.isCopyUsernameToClipboardCalloutHidden}
target={this.usernameTextFieldDomElement.parentElement}
isBeakVisible={false}
className={"clipboard-callout"}
directionalHint={DirectionalHint.rightCenter}
>
{strings.UsernameCopiedLabel}
</Callout>
}
</div>
</div>
}
@ -143,15 +178,31 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
disabled={true}
canRevealPassword={true}
defaultValue={this.decryptedData.password}
componentRef={(input: ITextField) => {
this.passwordTextFieldDomElement = this.helper.functions.getDeepOrDefault(input, "_textElement.current", null);
}}
onRenderSuffix={() => {
if(!showCopyToClipboard) {
return <></>;
}
return <Icon iconName={"Copy"} className="copy-icon" onClick={() => { this.copyToClipboard(this.decryptedData.password); }} />
return (<Icon iconName={"Copy"} className="copy-icon" onClick={() => {
this.copyToClipboard(this.decryptedData.password, true);
}} />);
}}
/>
{showCopyToClipboard && this.helper.functions.isset(this.passwordTextFieldDomElement) &&
<Callout
hidden={this.state.isCopyPasswordToClipboardCalloutHidden}
target={this.passwordTextFieldDomElement.parentElement}
isBeakVisible={false}
className={"clipboard-callout"}
directionalHint={DirectionalHint.rightCenter}
>
{strings.PasswordCopiedLabel}
</Callout>
}
</div>
</div>
}
@ -168,7 +219,7 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
<div className="spfxappdev-grid-row grid-footer">
<div className="spfxappdev-grid-col spfxappdev-sm12">
{!this.helper.functions.isNullOrEmpty(this.props.masterPW) &&
{!this.helper.functions.isNullOrEmpty(this.currentMasterPW) &&
<DefaultButton onClick={() => {
this.closeVault();
}}>
@ -205,7 +256,7 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
this.enteredMasterPW = newValue;
this.setState({
isSaveButtonDisabled: this.isSaveButtonDisabled()
})
});
}}
/>
</div>
@ -268,9 +319,10 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
<div className="spfxappdev-grid-col spfxappdev-sm12">
<PrimaryButton disabled={this.state.isSaveButtonDisabled} onClick={() => {
this.isNewVault = false;
let encryptedMaster = this.props.masterPW;
let encryptedMaster = this.currentMasterPW;
if(!this.enteredMasterPW.IsEmpty()) {
encryptedMaster = this.props.passwordVaultService.setMasterPassword(this.enteredMasterPW);
this.currentMasterPW = encryptedMaster;
}
this.encryptedData = this.props.passwordVaultService.encryptData({
@ -288,7 +340,7 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
{strings.SaveLabel}
</PrimaryButton>
{!this.helper.functions.isNullOrEmpty(this.props.masterPW) &&
{!this.helper.functions.isNullOrEmpty(this.currentMasterPW) &&
<DefaultButton onClick={() => {
this.closeVault();
}}>
@ -305,7 +357,7 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
private renderOpenVaultForm(): JSX.Element {
if(this.helper.functions.isNullOrEmpty(this.props.masterPW)) {
if(this.helper.functions.isNullOrEmpty(this.currentMasterPW)) {
return (<></>);
}
@ -328,6 +380,11 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
onChange={(ev: any, newValue: string) => {
this.enteredMasterPW = newValue;
}}
onKeyUp={(ev: React.KeyboardEvent<HTMLInputElement>) => {
if(ev.keyCode == 13) {
this.onOpenVault();
}
}}
/>
</div>
</div>
@ -368,7 +425,7 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
private onOpenVault(): void {
const props: IPasswordVaultProps = this.props;
const isCorrectPW = props.passwordVaultService.open(this.enteredMasterPW, props.masterPW);
const isCorrectPW = props.passwordVaultService.open(this.enteredMasterPW, this.currentMasterPW);
this.enteredMasterPW = "";
if(isCorrectPW) {
@ -385,7 +442,21 @@ export default class PasswordVault extends SPFxAppDevWebPartComponent<PasswordVa
});
}
private copyToClipboard(text: string): void {
window.navigator.clipboard.writeText(text);
private copyToClipboard(text: string, isPasswordField: boolean): void {
window.navigator.clipboard.writeText(text).then(() => {
this.setState({
isCopyPasswordToClipboardCalloutHidden: !isPasswordField,
isCopyUsernameToClipboardCalloutHidden: isPasswordField
});
window.setTimeout(() => {
this.setState({
isCopyPasswordToClipboardCalloutHidden: true,
isCopyUsernameToClipboardCalloutHidden: true
});
}, 2000);
});
}
}

View File

@ -12,5 +12,7 @@ define([], function() {
"SaveLabel": "Speichern",
"WrongPasswordLabel": "Falsches Passwort",
"OpenVaultLabel": "Tresor entsperren",
"UsernameCopiedLabel": "Benutzername kopiert",
"PasswordCopiedLabel": "Passwort kopiert"
}
});

View File

@ -12,5 +12,7 @@ define([], function() {
"SaveLabel": "Save",
"WrongPasswordLabel": "Wrong Password",
"OpenVaultLabel": "Open vault",
"UsernameCopiedLabel": "Username copied",
"PasswordCopiedLabel": "Password copied"
}
});

View File

@ -11,6 +11,8 @@ declare interface IPasswordVaultWebPartStrings {
SaveLabel: string;
WrongPasswordLabel: string;
OpenVaultLabel: string;
UsernameCopiedLabel: string;
PasswordCopiedLabel: string;
}
declare module 'PasswordVaultWebPartStrings' {