Skip to content

Commit f52b42d

Browse files
committed
feat(new tool): Passphrase Generator
Fix CorentinTh#1242 and CorentinTh#1228
1 parent 318fb6e commit f52b42d

File tree

7 files changed

+168
-9
lines changed

7 files changed

+168
-9
lines changed

components.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,11 @@ declare module '@vue/runtime-core' {
144144
NMenu: typeof import('naive-ui')['NMenu']
145145
NScrollbar: typeof import('naive-ui')['NScrollbar']
146146
NSlider: typeof import('naive-ui')['NSlider']
147+
NSpace: typeof import('naive-ui')['NSpace']
147148
NSwitch: typeof import('naive-ui')['NSwitch']
148149
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
149150
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
151+
PassphraseGenerator: typeof import('./src/tools/passphrase-generator/passphrase-generator.vue')['default']
150152
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
151153
PdfSignatureChecker: typeof import('./src/tools/pdf-signature-checker/pdf-signature-checker.vue')['default']
152154
PdfSignatureDetails: typeof import('./src/tools/pdf-signature-checker/components/pdf-signature-details.vue')['default']

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
"pinia": "^2.0.34",
8383
"plausible-tracker": "^0.3.8",
8484
"qrcode": "^1.5.1",
85+
"silly-password-generator": "^1.0.28",
8586
"sql-formatter": "^13.0.0",
8687
"ua-parser-js": "^1.0.35",
8788
"ulid": "^2.3.0",

pnpm-lock.yaml

Lines changed: 21 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/composable/queryParams.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { useRouteQuery } from '@vueuse/router';
22
import { computed } from 'vue';
3+
import { useStorage } from '@vueuse/core';
34

4-
export { useQueryParam };
5+
export { useQueryParam, useQueryParamOrStorage };
56

67
const transformers = {
78
number: {
@@ -16,6 +17,12 @@ const transformers = {
1617
fromQuery: (value: string) => value.toLowerCase() === 'true',
1718
toQuery: (value: boolean) => (value ? 'true' : 'false'),
1819
},
20+
object: {
21+
fromQuery: (value: string) => {
22+
return JSON.parse(value);
23+
},
24+
toQuery: (value: object) => JSON.stringify(value),
25+
},
1926
};
2027

2128
function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue: T }) {
@@ -33,3 +40,27 @@ function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue:
3340
},
3441
});
3542
}
43+
44+
function useQueryParamOrStorage<T>({ name, storageName, defaultValue }: { name: string; storageName: string; defaultValue: T }) {
45+
const type = typeof defaultValue;
46+
const transformer = transformers[type as keyof typeof transformers] ?? transformers.string;
47+
48+
const storageRef = useStorage(storageName, defaultValue);
49+
const proxyDefaultValue = transformer.toQuery(defaultValue as never);
50+
const proxy = useRouteQuery(name, proxyDefaultValue);
51+
52+
const r = ref(defaultValue);
53+
54+
watch(r,
55+
(value) => {
56+
proxy.value = transformer.toQuery(value as never);
57+
storageRef.value = value as never;
58+
},
59+
{ deep: true });
60+
61+
r.value = (proxy.value && proxy.value !== proxyDefaultValue
62+
? transformer.fromQuery(proxy.value) as unknown as T
63+
: storageRef.value as T) as never;
64+
65+
return r;
66+
}

src/tools/index.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { tool as base64FileConverter } from './base64-file-converter';
22
import { tool as base64StringConverter } from './base64-string-converter';
33
import { tool as basicAuthGenerator } from './basic-auth-generator';
44
import { tool as emailNormalizer } from './email-normalizer';
5+
import { tool as passphraseGenerator } from './passphrase-generator';
56

67
import { tool as asciiTextDrawer } from './ascii-text-drawer';
78

@@ -88,7 +89,20 @@ import { tool as yamlViewer } from './yaml-viewer';
8889
export const toolsByCategory: ToolCategory[] = [
8990
{
9091
name: 'Crypto',
91-
components: [tokenGenerator, hashText, bcrypt, uuidGenerator, ulidGenerator, cypher, bip39, hmacGenerator, rsaKeyPairGenerator, passwordStrengthAnalyser, pdfSignatureChecker],
92+
components: [
93+
tokenGenerator,
94+
passphraseGenerator,
95+
hashText,
96+
bcrypt,
97+
uuidGenerator,
98+
ulidGenerator,
99+
cypher,
100+
bip39,
101+
hmacGenerator,
102+
rsaKeyPairGenerator,
103+
passwordStrengthAnalyser,
104+
pdfSignatureChecker,
105+
],
92106
},
93107
{
94108
name: 'Converter',
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { ArrowsShuffle } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'Passphrase Generator',
6+
path: '/passphrase-generator',
7+
description: 'Generate random memoizable Passphrases',
8+
keywords: ['passphrase', 'random', 'password', 'generator'],
9+
component: () => import('./passphrase-generator.vue'),
10+
icon: ArrowsShuffle,
11+
createdAt: new Date('2024-08-15'),
12+
});
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<script setup lang="ts">
2+
import { generateSillyPassword } from 'silly-password-generator';
3+
import { useCopy } from '@/composable/copy';
4+
import { useQueryParamOrStorage } from '@/composable/queryParams';
5+
import { computedRefreshable } from '@/composable/computedRefreshable';
6+
7+
const count = useQueryParamOrStorage({ name: 'count', storageName: 'pass-generator:count', defaultValue: 1 });
8+
const words = useQueryParamOrStorage({ name: 'words', storageName: 'pass-generator:words', defaultValue: 5 });
9+
const capitalize = useQueryParamOrStorage({ name: 'capitalize', storageName: 'pass-generator:capitalize', defaultValue: false });
10+
const saltChars = useQueryParamOrStorage({ name: 'salt', storageName: 'pass-generator:salt', defaultValue: '' });
11+
const separator = useQueryParamOrStorage({ name: 'sep', storageName: 'pass-generator:sep', defaultValue: '-' });
12+
13+
const [passphrases, refreshPassphrases] = computedRefreshable(() =>
14+
Array.from({ length: count.value },
15+
() => generateSillyPassword({
16+
capitalize: capitalize.value,
17+
wordCount: words.value,
18+
salt: saltChars.value,
19+
}).replace(/\s+/g, separator.value)).join('\n'),
20+
);
21+
22+
const { copy } = useCopy({ source: passphrases, text: 'Passphrase(s) copied to clipboard!' });
23+
</script>
24+
25+
<template>
26+
<div>
27+
<c-card>
28+
<n-form-item :label="`Words (${words})`" label-placement="left">
29+
<n-slider v-model:value="words" :step="1" :min="1" :max="512" mr-2 />
30+
<n-input-number v-model:value="words" size="small" />
31+
</n-form-item>
32+
33+
<n-space>
34+
<n-form-item label="Capitalize" label-placement="left">
35+
<n-switch v-model:value="capitalize" />
36+
</n-form-item>
37+
<n-form-item label="Separator" label-placement="left">
38+
<c-input-text
39+
v-model:value="separator"
40+
placeholder="Put separator char"
41+
/>
42+
</n-form-item>
43+
</n-space>
44+
45+
<n-form-item label="Ending Salt Chars" label-placement="left">
46+
<c-input-text
47+
v-model:value="saltChars"
48+
placeholder="Put characters to appended to end of passphrase"
49+
/>
50+
</n-form-item>
51+
52+
<n-form-item label="Number of passphrase to generate" label-placement="left">
53+
<n-input-number v-model:value="count" size="small" />
54+
</n-form-item>
55+
56+
<c-input-text
57+
v-model:value="passphrases"
58+
multiline
59+
placeholder="Passphrase..."
60+
readonly
61+
rows="3"
62+
autosize
63+
class="passphrase-display"
64+
word-wrap
65+
/>
66+
67+
<div mt-5 flex justify-center gap-3>
68+
<c-button @click="copy()">
69+
Copy
70+
</c-button>
71+
<c-button @click="refreshPassphrases">
72+
Refresh
73+
</c-button>
74+
</div>
75+
</c-card>
76+
</div>
77+
</template>
78+
79+
<style scoped lang="less">
80+
::v-deep(.passphrase-display) {
81+
textarea {
82+
text-align: center;
83+
}
84+
}
85+
</style>

0 commit comments

Comments
 (0)