-
Notifications
You must be signed in to change notification settings - Fork 7.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add crypto-js and nanoid , add add crypto demo page #4835
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,33 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import CryptoJS from 'crypto-js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export { CryptoJS }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const MIN_SECRET_LENGTH = 32; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export class Crypto<T extends object> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** Secret */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private readonly secret: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
constructor(secret: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (typeof secret === 'string' && secret.length < MIN_SECRET_LENGTH) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
throw new Error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
`Secret must be at least ${MIN_SECRET_LENGTH} characters long`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.secret = secret; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
decrypt(encrypted: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const decrypted = CryptoJS.AES.decrypt(encrypted, this.secret); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const dataString = decrypted.toString(CryptoJS.enc.Utf8); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return JSON.parse(dataString) as T; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// avoid parse error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+17
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Improve decrypt method security and error handling. The current implementation has several security considerations:
-decrypt(encrypted: string) {
+decrypt(encrypted: string): T | null {
+ if (!encrypted || typeof encrypted !== 'string') {
+ throw new Error('Invalid encrypted data');
+ }
+
const decrypted = CryptoJS.AES.decrypt(encrypted, this.secret);
+ if (!decrypted) {
+ throw new Error('Decryption failed');
+ }
+
const dataString = decrypted.toString(CryptoJS.enc.Utf8);
try {
return JSON.parse(dataString) as T;
} catch {
- // avoid parse error
+ console.warn('Failed to parse decrypted data');
return null;
}
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
encrypt(data: T): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const dataString = JSON.stringify(data); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const encrypted = CryptoJS.AES.encrypt(dataString, this.secret); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return encrypted.toString(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+28
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Enhance encrypt method security. The encryption implementation should be strengthened with:
-encrypt(data: T): string {
+encrypt(data: T): string {
+ if (!data || typeof data !== 'object') {
+ throw new Error('Invalid data for encryption');
+ }
+
const dataString = JSON.stringify(data);
- const encrypted = CryptoJS.AES.encrypt(dataString, this.secret);
+ // Generate a random IV for each encryption
+ const iv = CryptoJS.lib.WordArray.random(16);
+ const encrypted = CryptoJS.AES.encrypt(dataString, this.secret, {
+ iv,
+ mode: CryptoJS.mode.CBC,
+ padding: CryptoJS.pad.Pkcs7
+ });
+
+ // Include IV in the output for decryption
+ return JSON.stringify({
+ iv: iv.toString(),
+ content: encrypted.toString()
+ });
- return encrypted.toString();
} Note: The decrypt method will need to be updated to handle the new encrypted data format that includes the IV. 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { describe, expect, it } from 'vitest'; | ||
|
||
import { nanoid } from '../nanoid'; | ||
|
||
describe('nanoid', () => { | ||
it('create uuid', () => { | ||
const _nanoid = nanoid(); | ||
// console.log('uuid:', _nanoid); | ||
// expect(!!_nanoid).toBe(true); | ||
expect(typeof _nanoid).toBe('string'); | ||
expect(_nanoid.length).toBeGreaterThan(0); | ||
}); | ||
it('should generate unique ids', () => { | ||
const ids = new Set(); | ||
for (let i = 0; i < 1000; i++) { | ||
const id = nanoid(); | ||
expect(ids.has(id)).toBe(false); | ||
ids.add(id); | ||
} | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { nanoid } from 'nanoid'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
export * from './helpers'; | ||
export * from '@vben-core/shared/cache'; | ||
export * from '@vben-core/shared/color'; | ||
export * from '@vben-core/shared/crypto'; | ||
export * from '@vben-core/shared/utils'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -221,6 +221,15 @@ const routes: RouteRecordRaw[] = [ | |
title: 'Tanstack Query', | ||
}, | ||
}, | ||
{ | ||
name: 'CryptoDemo', | ||
path: '/demos/features/crypto', | ||
component: () => import('#/views/demos/features/crypto/index.vue'), | ||
meta: { | ||
icon: 'lucide:message-square-lock', | ||
title: 'Crypto Demo', | ||
}, | ||
}, | ||
Comment on lines
+224
to
+232
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Fix title formatting and use translation key pattern The route configuration needs to be updated to follow the established patterns in the codebase:
{
name: 'CryptoDemo',
path: '/demos/features/crypto',
component: () => import('#/views/demos/features/crypto/index.vue'),
meta: {
icon: 'lucide:message-square-lock',
- title: 'Crypto Demo',
+ title: $t('demos.features.crypto'),
+ keepAlive: true,
},
}, 🔗 Analysis chainFix title formatting and consider adding translations and keepAlive. While the route configuration is well-structured and appropriately placed, there are a few improvements to consider:
{
name: 'CryptoDemo',
path: '/demos/features/crypto',
component: () => import('#/views/demos/features/crypto/index.vue'),
meta: {
icon: 'lucide:message-square-lock',
- title: 'Crypto Demo',
+ title: $t('demos.features.crypto'),
+ keepAlive: true,
},
}, Let's verify if the translation key exists: 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Check if the translation key for crypto demo exists
rg -g '*.{ts,js,json}' "demos\.features\.crypto"
Length of output: 50 Script: #!/bin/bash
# Let's check the localization files and patterns used in the codebase
# First, find translation files
fd -e json -e yaml locales
# Then check other route titles to understand the translation pattern
rg -g '*.{ts,js}' 'title: \$t\(' routes/
Length of output: 119 Script: #!/bin/bash
# Let's check the route file itself to understand the pattern used for titles
rg -g '*.ts' 'title:' playground/src/router/routes/modules/demos.ts
# Also check for keepAlive usage in the same file
rg -g '*.ts' 'keepAlive:' playground/src/router/routes/modules/demos.ts
Length of output: 2981 |
||
], | ||
}, | ||
// 面包屑导航 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
<script setup lang="ts"> | ||
import { computed, ref, watch } from 'vue'; | ||
|
||
import { | ||
Card, | ||
Col, | ||
Divider, | ||
Input, | ||
RadioButton, | ||
RadioGroup, | ||
Row, | ||
Textarea, | ||
} from 'ant-design-vue'; | ||
|
||
import { | ||
aesDecryptFn, | ||
aesEncryptFn, | ||
desDecryptFn, | ||
desEncryptFn, | ||
hashingFn, | ||
} from './inner/crypto-hooks'; | ||
|
||
const targetInputTextRef = ref('testtest'); | ||
const secretKeyRef = ref('3mbzyxbpg6613ql'); | ||
const encryModeRef = ref('aes'); | ||
|
||
const aseEncryptTextRef = ref(''); | ||
const needDecryptTextRef = ref(''); | ||
Comment on lines
+23
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Security: Remove hardcoded sensitive values. The following security issues need to be addressed:
Apply this diff to remove hardcoded values: -const targetInputTextRef = ref('testtest');
-const secretKeyRef = ref('3mbzyxbpg6613ql');
+const targetInputTextRef = ref('');
+const secretKeyRef = ref(''); Consider:
|
||
|
||
const doAesEncryptFn = () => { | ||
if (!targetInputTextRef.value?.trim() || !secretKeyRef.value?.trim()) { | ||
return; | ||
} | ||
try { | ||
const text = | ||
encryModeRef.value === 'aes' | ||
? aesEncryptFn(targetInputTextRef.value, secretKeyRef.value) | ||
: desEncryptFn(targetInputTextRef.value, secretKeyRef.value); | ||
aseEncryptTextRef.value = text; | ||
needDecryptTextRef.value = text; | ||
} catch (error) { | ||
console.error('Encryption failed:', error); | ||
// Consider using a notification system to show errors | ||
} | ||
}; | ||
Comment on lines
+30
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve error handling and function naming. The encryption function has several areas for improvement:
Consider this improved implementation: -const doAesEncryptFn = () => {
+const performEncryption = () => {
if (!targetInputTextRef.value?.trim() || !secretKeyRef.value?.trim()) {
+ // Use your app's notification system (e.g., message.error)
+ showError('Please provide both input text and secret key');
return;
}
+
+ // Validate key strength
+ if (secretKeyRef.value.length < 16) {
+ showError('Secret key must be at least 16 characters long');
+ return;
+ }
try {
const text =
encryModeRef.value === 'aes'
? aesEncryptFn(targetInputTextRef.value, secretKeyRef.value)
: desEncryptFn(targetInputTextRef.value, secretKeyRef.value);
aseEncryptTextRef.value = text;
needDecryptTextRef.value = text;
} catch (error) {
- console.error('Encryption failed:', error);
- // Consider using a notification system to show errors
+ showError(`Encryption failed: ${error.message}`);
}
};
|
||
|
||
const parseTextComputed = computed(() => { | ||
const value = | ||
encryModeRef.value === 'aes' | ||
? aesDecryptFn(needDecryptTextRef.value, secretKeyRef.value) | ||
: desDecryptFn(needDecryptTextRef.value, secretKeyRef.value); | ||
return value; | ||
}); | ||
watch( | ||
[ | ||
() => targetInputTextRef.value, | ||
() => secretKeyRef.value, | ||
() => encryModeRef.value, | ||
], | ||
() => { | ||
doAesEncryptFn(); | ||
}, | ||
{ | ||
immediate: true, | ||
}, | ||
); | ||
</script> | ||
|
||
<template> | ||
<div class="box-border w-full px-2"> | ||
<Card class="mb-3" title="使用Crypto加密"> | ||
<div>要操作的字符串</div> | ||
<Input | ||
v-model:value="targetInputTextRef" | ||
placeholder="请输入要操作的字符串" | ||
/> | ||
<div>使用的密钥</div> | ||
<AInput v-model:value="secretKeyRef" /> | ||
</Card> | ||
<Row> | ||
<Col :lg="12" :md="24" :sm="24" :xl="12" :xs="24"> | ||
<Card class="mr-1" title="Hashing"> | ||
<div | ||
class="px-10px box-border flex w-full flex-col divide-y overflow-x-hidden" | ||
> | ||
<div | ||
class="flex h-auto w-full flex-row justify-between overflow-hidden" | ||
> | ||
<div class="flex-none"> | ||
<span :style="{ paddingRight: '10px' }">md5:</span> | ||
</div> | ||
<div class="h-auto flex-1 overflow-x-hidden"> | ||
<div class="w-full text-wrap break-words text-right"> | ||
{{ hashingFn(targetInputTextRef, 'MD5') }} | ||
</div> | ||
</div> | ||
</div> | ||
<div | ||
class="flex h-auto w-full flex-row justify-between overflow-hidden" | ||
> | ||
<div class="flex-none"> | ||
<span :style="{ paddingRight: '10px' }">SHA1:</span> | ||
</div> | ||
<div class="h-auto flex-1 overflow-x-hidden"> | ||
<div class="w-full text-wrap break-words text-right"> | ||
{{ hashingFn(targetInputTextRef, 'SHA1') }} | ||
</div> | ||
</div> | ||
</div> | ||
<div | ||
class="flex h-auto w-full flex-row justify-between overflow-hidden" | ||
> | ||
<div class="flex-none"> | ||
<span :style="{ paddingRight: '10px' }">SHA256:</span> | ||
</div> | ||
<div class="h-auto flex-1 overflow-x-hidden"> | ||
<div class="w-full text-wrap break-words text-right"> | ||
{{ hashingFn(targetInputTextRef, 'SHA256') }} | ||
</div> | ||
</div> | ||
</div> | ||
<div | ||
class="flex h-auto w-full flex-row justify-between overflow-hidden" | ||
> | ||
<div class="flex-none"> | ||
<span :style="{ paddingRight: '10px' }">SHA512:</span> | ||
</div> | ||
<div class="h-auto flex-1 overflow-x-hidden"> | ||
<div class="w-full text-wrap break-words text-right"> | ||
{{ hashingFn(targetInputTextRef, 'SHA512') }} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</Card> | ||
</Col> | ||
<Col :lg="12" :md="24" :sm="24" :xl="12" :xs="24"> | ||
<Card title="Ciphers"> | ||
<div | ||
class="px-10px box-border flex w-full flex-col overflow-x-hidden" | ||
> | ||
<div | ||
class="box-border flex flex-row items-center justify-between pb-2" | ||
> | ||
<div class="flex-none">加密方式:</div> | ||
<div class="flex-1 text-center"> | ||
<RadioGroup | ||
v-model:value="encryModeRef" | ||
button-style="solid" | ||
size="small" | ||
> | ||
<RadioButton value="aes">AES</RadioButton> | ||
<RadioButton value="des">DES</RadioButton> | ||
</RadioGroup> | ||
</div> | ||
</div> | ||
<Divider /> | ||
<div v-if="encryModeRef === 'aes'"> | ||
<div | ||
class="flex h-auto w-full flex-row justify-between overflow-hidden" | ||
> | ||
<div class="flex-none"> | ||
<span :style="{ paddingRight: '10px' }">加密:</span> | ||
</div> | ||
<div class="h-auto flex-1 overflow-x-hidden"> | ||
<div class="w-full text-wrap break-words text-right"> | ||
{{ aseEncryptTextRef }} | ||
</div> | ||
</div> | ||
</div> | ||
<Divider /> | ||
<div> | ||
<div>解密:</div> | ||
<div>要解密的文本</div> | ||
<Textarea | ||
v-model:value="needDecryptTextRef" | ||
placeholder="请输入要操作的字符串" | ||
/> | ||
<div>解密后的原文</div> | ||
<Textarea :value="parseTextComputed" readonly /> | ||
</div> | ||
</div> | ||
<div v-else-if="encryModeRef === 'des'"> | ||
<div | ||
class="flex h-auto w-full flex-row justify-between overflow-hidden" | ||
> | ||
<div class="flex-none"> | ||
<span :style="{ paddingRight: '10px' }">加密:</span> | ||
</div> | ||
<div class="h-auto flex-1 overflow-x-hidden"> | ||
<div class="w-full text-wrap break-words text-right"> | ||
{{ aseEncryptTextRef }} | ||
</div> | ||
</div> | ||
</div> | ||
<Divider /> | ||
<div> | ||
<div>解密:</div> | ||
<div>要解密的文本</div> | ||
<ATextarea | ||
v-model:value="needDecryptTextRef" | ||
placeholder="请输入要操作的字符串" | ||
/> | ||
<div>解密后的原文</div> | ||
<Textarea :value="parseTextComputed" readonly /> | ||
</div> | ||
</div> | ||
</div> | ||
</Card> | ||
</Col> | ||
</Row> | ||
</div> | ||
</template> | ||
|
||
<style lang="scss" scoped></style> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enhance secret key validation and handling.
The current implementation only validates the length of the secret. Consider these security improvements: