Skip to content

Commit

Permalink
feat: the cache can be configured to be encrypted
Browse files Browse the repository at this point in the history
  • Loading branch information
anncwb committed Nov 23, 2020
1 parent 3a132f3 commit 234c1d1
Show file tree
Hide file tree
Showing 20 changed files with 300 additions and 185 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.zh_CN.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## Wip

### ✨ Features

- 缓存可以配置是否加密
- 多语言支持

### 🎫 Chores

- 移除 messageSetting 配置
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@
"vditor": "^3.6.3",
"vue": "^3.0.2",
"vue-i18n": "^9.0.0-beta.8",
"vue-router": "^4.0.0-rc.3",
"vue-router": "^4.0.0-rc.5",
"vuex": "^4.0.0-rc.1",
"vuex-module-decorators": "^1.0.1",
"xlsx": "^0.16.8",
"xlsx": "^0.16.9",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
"@iconify/json": "^1.1.261",
"@iconify/json": "^1.1.262",
"@ls-lint/ls-lint": "^1.9.2",
"@purge-icons/generated": "^0.4.1",
"@types/echarts": "^4.9.1",
Expand All @@ -72,7 +72,7 @@
"cross-env": "^7.0.2",
"dot-prop": "^6.0.1",
"dotenv": "^8.2.0",
"eslint": "^7.13.0",
"eslint": "^7.14.0",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-vue": "^7.1.0",
Expand Down
6 changes: 5 additions & 1 deletion src/components/Application/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import AppLocalPicker from './src/AppLocalPicker.vue';
import AppFooterToolbar from './src/AppFooterToolbar.vue';
import { withInstall } from '../util';

export { AppLocalPicker };
export { AppLocalPicker, AppFooterToolbar };

export default withInstall(AppLocalPicker, AppFooterToolbar);
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
</template>
<script lang="ts">
import { defineComponent, computed, unref } from 'vue';
import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum';
import { appStore } from '/@/store/modules/app';
import { menuStore } from '/@/store/modules/menu';
export default defineComponent({
name: 'AppFooter',
name: 'AppFooterToolbar',
setup() {
const getMiniWidth = computed(() => {
const {
Expand Down
7 changes: 3 additions & 4 deletions src/components/Authority/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { App } from 'vue';
import Authority from './src/index.vue';

export default (app: App): void => {
app.component(Authority.name, Authority);
};
import { withInstall } from '../util';

export default withInstall(Authority);

export { Authority };
1 change: 0 additions & 1 deletion src/components/Footer/index.ts

This file was deleted.

3 changes: 1 addition & 2 deletions src/components/registerGlobComp.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Icon from './Icon/index';
import Button from './Button/index.vue';
import { AppFooter } from './Footer';
import {
// Need
Button as AntButton,
Expand Down Expand Up @@ -35,7 +34,7 @@ import {
} from 'ant-design-vue';
import { getApp } from '/@/setup/App';

const compList = [Icon, Button, AntButton.Group, AppFooter];
const compList = [Icon, Button, AntButton.Group];

// Fix hmr multiple registered components
let registered = false;
Expand Down
9 changes: 9 additions & 0 deletions src/components/util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import type { VNodeChild } from 'vue';
import type { App, Component } from 'vue';

export function withInstall(...components: Component[]) {
return (app: App) => {
components.forEach((comp) => {
comp.name && app.component(comp.name, comp);
});
};
}

export function convertToUnit(
str: string | number | null | undefined,
Expand Down
10 changes: 0 additions & 10 deletions src/settings/cipherSetting.ts

This file was deleted.

13 changes: 13 additions & 0 deletions src/settings/encryptionSetting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { isDevMode } from '/@/utils/env';

// System default cache time, in seconds
export const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;

// aes encryption key
export const cacheCipher = {
key: '_12345678901234@',
iv: '@12345678901234_',
};

// Whether the system cache is encrypted using aes
export const enableStorageEncryption = !isDevMode();
78 changes: 78 additions & 0 deletions src/utils/cache/cookie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { DEFAULT_CACHE_TIME } from '../../settings/encryptionSetting';
import { getStorageShortName } from '/@/utils/helper/envHelper';
import { cacheCipher } from '/@/settings/encryptionSetting';
import Encryption from '/@/utils/encryption/aesEncryption';

export default class WebCookie {
private encryption: Encryption;
private hasEncrypt: boolean;

constructor(hasEncrypt = true, key = cacheCipher.key, iv = cacheCipher.iv) {
const encryption = new Encryption({ key, iv });
this.encryption = encryption;
this.hasEncrypt = hasEncrypt;
}

private getKey(key: string) {
return `${getStorageShortName()}${key}`.toUpperCase();
}

/**
* Add cookie
* @param name cookie key
* @param value cookie value
* @param expire
* If the expiration time is not set, the default management browser will automatically delete
* e.g:
* cookieData.set('name','value',)
*/
setCookie(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
value = this.hasEncrypt ? this.encryption.encryptByAES(JSON.stringify(value)) : value;
document.cookie = this.getKey(key) + '=' + value + '; Max-Age=' + expire;
}

/**
* Get the cook value according to the key
* @param key cookie key
*/
getCookie(key: string) {
const arr = document.cookie.split('; ');
for (let i = 0; i < arr.length; i++) {
const arr2 = arr[i].split('=');
if (arr2[0] === this.getKey(key)) {
let message: any = null;
const str = arr2[1];
if (this.hasEncrypt && str) {
message = this.encryption.decryptByAES(str);
try {
return JSON.parse(message);
} catch (e) {
return str;
}
}
return str;
}
}
return '';
}

/**
* Delete cookie based on cookie key
* @param key cookie key
*/
removeCookie(key: string) {
this.setCookie(key, 1, -1);
}

/**
* clear cookie
*/
clearCookie(): void {
const keys = document.cookie.match(/[^ =;]+(?==)/g);
if (keys) {
for (let i = keys.length; i--; ) {
document.cookie = keys[i] + '=0;expires=' + new Date(0).toUTCString();
}
}
}
}
9 changes: 6 additions & 3 deletions src/utils/storage/index.ts → src/utils/cache/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { getStorageShortName } from '/@/utils/helper/envHelper';
import { createStorage as create } from './Storage';

// debug模式下不加密
import { createStorage as create } from './storageCache';
import { enableStorageEncryption } from '/@/settings/encryptionSetting';

const createOptions = (storage = sessionStorage) => {
return {
// No encryption in debug mode
hasEncrypt: enableStorageEncryption,
storage,
prefixKey: getStorageShortName(),
};
};

export const WebStorage = create(createOptions());

export const createStorage = (storage: Storage = sessionStorage) => {
return create(createOptions(storage))!;
};

export default WebStorage;
108 changes: 108 additions & 0 deletions src/utils/cache/storageCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';
import { cacheCipher } from '/@/settings/encryptionSetting';
import Encryption, { EncryptionParams } from '/@/utils/encryption/aesEncryption';

export interface CreateStorageParams extends EncryptionParams {
storage: Storage;

hasEncrypt: boolean;
}
export const createStorage = ({
prefixKey = '',
storage = sessionStorage,
key = cacheCipher.key,
iv = cacheCipher.iv,
hasEncrypt = true,
} = {}) => {
if (hasEncrypt && [key.length, iv.length].some((item) => item !== 16)) {
throw new Error('When hasEncrypt is true, the key or iv must be 16 bits!');
}

const encryption = new Encryption({ key, iv });

/**
*Cache class
*Construction parameters can be passed into sessionStorage, localStorage,
* @class Cache
* @example
*/
const WebStorage = class WebStorage {
private storage: Storage;
private prefixKey?: string;
private encryption: Encryption;
private hasEncrypt: boolean;
/**
*
* @param {*} storage
*/
constructor() {
this.storage = storage;
this.prefixKey = prefixKey;
this.encryption = encryption;
this.hasEncrypt = hasEncrypt;
}

private getKey(key: string) {
return `${this.prefixKey}${key}`.toUpperCase();
}

/**
*
* Set cache
* @param {string} key
* @param {*} value
* @expire Expiration time in seconds
* @memberof Cache
*/
set(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
const stringData = JSON.stringify({
value,
expire: expire !== null ? new Date().getTime() + expire * 1000 : null,
});
const stringifyValue = this.hasEncrypt
? this.encryption.encryptByAES(stringData)
: stringData;
this.storage.setItem(this.getKey(key), stringifyValue);
}

/**
*Read cache
* @param {string} key
* @memberof Cache
*/
get(key: string, def: any = null): any {
const item = this.storage.getItem(this.getKey(key));
if (item) {
try {
const decItem = this.hasEncrypt ? this.encryption.decryptByAES(item) : item;
const data = JSON.parse(decItem);
const { value, expire } = data;
if (expire === null || expire >= new Date().getTime()) {
return value;
}
this.remove(this.getKey(key));
} catch (e) {
return def;
}
}
return def;
}

/**
* Delete cache based on key
* @param {string} key
* @memberof Cache
*/
remove(key: string) {
this.storage.removeItem(this.getKey(key));
}

/**
* Delete all caches of this instance
*/
clear(): void {
this.storage.clear();
}
};
return new WebStorage();
};
35 changes: 35 additions & 0 deletions src/utils/encryption/aesEncryption.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import CryptoES from 'crypto-es';
export interface EncryptionParams {
key: string;
iv: string;
}
export class Encryption {
private key;

private iv;

constructor(opt: EncryptionParams) {
const { key, iv } = opt;
this.key = CryptoES.enc.Utf8.parse(key);
this.iv = CryptoES.enc.Utf8.parse(iv);
}

get getOpt(): CryptoES.lib.CipherCfg {
return {
mode: CryptoES.mode.CBC as any,
padding: CryptoES.pad.Pkcs7,
iv: this.iv,
};
}

encryptByAES(str: string) {
const encrypted = CryptoES.AES.encrypt(str, this.key, this.getOpt);
return encrypted.toString();
}

decryptByAES(str: string) {
const decrypted = CryptoES.AES.decrypt(str, this.key, this.getOpt);
return decrypted.toString(CryptoES.enc.Utf8);
}
}
export default Encryption;
File renamed without changes.
2 changes: 1 addition & 1 deletion src/utils/file/download.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { dataURLtoBlob, urlToBase64 } from './base64';
import { dataURLtoBlob, urlToBase64 } from './base64Conver';

/**
* Download online pictures
Expand Down
2 changes: 1 addition & 1 deletion src/utils/helper/persistent.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createStorage } from '/@/utils/storage';
import { createStorage } from '/@/utils/cache';
import { isIeFn } from '/@/utils/browser';

import { BASE_LOCAL_CACHE_KEY, BASE_SESSION_CACHE_KEY } from '/@/enums/cacheEnum';
Expand Down
Loading

0 comments on commit 234c1d1

Please sign in to comment.