Merge 5035cb888c into 07eea0f484
				
					
				
			This commit is contained in:
		
						commit
						b0a2b12d98
					
				
							
								
								
									
										30
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | // For format details, see https://aka.ms/devcontainer.json. For config options, see the | ||||||
|  | // README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node | ||||||
|  | { | ||||||
|  | 	"name": "Node.js & TypeScript", | ||||||
|  | 	// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile | ||||||
|  | 	"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm", | ||||||
|  | 
 | ||||||
|  | 	// Features to add to the dev container. More info: https://containers.dev/features. | ||||||
|  | 	// "features": {}, | ||||||
|  | 
 | ||||||
|  | 	// Use 'forwardPorts' to make a list of ports inside the container available locally. | ||||||
|  | 	// "forwardPorts": [], | ||||||
|  | 
 | ||||||
|  | 	// Use 'postCreateCommand' to run commands after the container is created. | ||||||
|  | 	// "postCreateCommand": "yarn install", | ||||||
|  | 
 | ||||||
|  | 	// Configure tool-specific properties. | ||||||
|  | 	"customizations": { | ||||||
|  | 		"vscode": { | ||||||
|  | 			"extensions": [ | ||||||
|  | 				"Vue.volar", | ||||||
|  | 				"Lokalise.i18n-ally", | ||||||
|  | 				"dbaeumer.vscode-eslint" | ||||||
|  | 			] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. | ||||||
|  | 	// "remoteUser": "root" | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -13,6 +13,7 @@ dist | |||||||
| dist-ssr | dist-ssr | ||||||
| coverage | coverage | ||||||
| *.local | *.local | ||||||
|  | .pnpm-store | ||||||
| 
 | 
 | ||||||
| /cypress/videos/ | /cypress/videos/ | ||||||
| /cypress/screenshots/ | /cypress/screenshots/ | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -98,6 +98,7 @@ declare module '@vue/runtime-core' { | |||||||
|     IconMdiEye: typeof import('~icons/mdi/eye')['default'] |     IconMdiEye: typeof import('~icons/mdi/eye')['default'] | ||||||
|     IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default'] |     IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default'] | ||||||
|     IconMdiHeart: typeof import('~icons/mdi/heart')['default'] |     IconMdiHeart: typeof import('~icons/mdi/heart')['default'] | ||||||
|  |     IconMdiRefresh: typeof import('~icons/mdi/refresh')['default'] | ||||||
|     IconMdiSearch: typeof import('~icons/mdi/search')['default'] |     IconMdiSearch: typeof import('~icons/mdi/search')['default'] | ||||||
|     IconMdiTranslate: typeof import('~icons/mdi/translate')['default'] |     IconMdiTranslate: typeof import('~icons/mdi/translate')['default'] | ||||||
|     IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default'] |     IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default'] | ||||||
| @ -135,13 +136,20 @@ declare module '@vue/runtime-core' { | |||||||
|     NConfigProvider: typeof import('naive-ui')['NConfigProvider'] |     NConfigProvider: typeof import('naive-ui')['NConfigProvider'] | ||||||
|     NDivider: typeof import('naive-ui')['NDivider'] |     NDivider: typeof import('naive-ui')['NDivider'] | ||||||
|     NEllipsis: typeof import('naive-ui')['NEllipsis'] |     NEllipsis: typeof import('naive-ui')['NEllipsis'] | ||||||
|  |     NForm: typeof import('naive-ui')['NForm'] | ||||||
|  |     NFormItem: typeof import('naive-ui')['NFormItem'] | ||||||
|     NH1: typeof import('naive-ui')['NH1'] |     NH1: typeof import('naive-ui')['NH1'] | ||||||
|     NH3: typeof import('naive-ui')['NH3'] |     NH3: typeof import('naive-ui')['NH3'] | ||||||
|     NIcon: typeof import('naive-ui')['NIcon'] |     NIcon: typeof import('naive-ui')['NIcon'] | ||||||
|  |     NImage: typeof import('naive-ui')['NImage'] | ||||||
|  |     NInputNumber: typeof import('naive-ui')['NInputNumber'] | ||||||
|     NLayout: typeof import('naive-ui')['NLayout'] |     NLayout: typeof import('naive-ui')['NLayout'] | ||||||
|     NLayoutSider: typeof import('naive-ui')['NLayoutSider'] |     NLayoutSider: typeof import('naive-ui')['NLayoutSider'] | ||||||
|     NMenu: typeof import('naive-ui')['NMenu'] |     NMenu: typeof import('naive-ui')['NMenu'] | ||||||
|  |     NProgress: typeof import('naive-ui')['NProgress'] | ||||||
|  |     NSlider: typeof import('naive-ui')['NSlider'] | ||||||
|     NSpace: typeof import('naive-ui')['NSpace'] |     NSpace: typeof import('naive-ui')['NSpace'] | ||||||
|  |     NSwitch: typeof import('naive-ui')['NSwitch'] | ||||||
|     NTable: typeof import('naive-ui')['NTable'] |     NTable: typeof import('naive-ui')['NTable'] | ||||||
|     NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] |     NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] | ||||||
|     OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] |     OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] | ||||||
|  | |||||||
| @ -444,7 +444,7 @@ tools: | |||||||
|   otp-generator: |   otp-generator: | ||||||
|     title: OTP-Code-Generator |     title: OTP-Code-Generator | ||||||
|     description: >- |     description: >- | ||||||
|       Generiere und validiere zeitbasierte OTPs (Einmalpasswörter) für |       Generiere und validiere zeitbasierte und ereignisgesteuertes OTPs (Einmalpasswörter) für | ||||||
|       Multi-Faktor-Authentifizierung. |       Multi-Faktor-Authentifizierung. | ||||||
|   url-encoder: |   url-encoder: | ||||||
|     title: Kodieren/Decodieren von URL-formatierten Zeichenfolgen |     title: Kodieren/Decodieren von URL-formatierten Zeichenfolgen | ||||||
|  | |||||||
| @ -383,7 +383,7 @@ tools: | |||||||
| 
 | 
 | ||||||
|   otp-generator: |   otp-generator: | ||||||
|     title: OTP code generator |     title: OTP code generator | ||||||
|     description: Generate and validate time-based OTP (one time password) for multi-factor authentication. |     description: Generate and validate time-based and event-based OTP (one time password) for multi-factor authentication. | ||||||
| 
 | 
 | ||||||
|   url-encoder: |   url-encoder: | ||||||
|     title: Encode/decode URL-formatted strings |     title: Encode/decode URL-formatted strings | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| import { useTimestamp } from '@vueuse/core'; | import { useTimestamp } from '@vueuse/core'; | ||||||
| import { useThemeVars } from 'naive-ui'; | import { useThemeVars } from 'naive-ui'; | ||||||
| import { useQRCode } from '../qr-code-generator/useQRCode'; | import { useQRCode } from '../qr-code-generator/useQRCode'; | ||||||
| import { base32toHex, buildKeyUri, generateSecret, generateTOTP, getCounterFromTime } from './otp.service'; | import { base32toHex, buildKeyUri, generateHOTP, generateSecret, generateTOTP, getCounterFromTime } from './otp.service'; | ||||||
| import TokenDisplay from './token-display.vue'; | import TokenDisplay from './token-display.vue'; | ||||||
| import { useStyleStore } from '@/stores/style.store'; | import { useStyleStore } from '@/stores/style.store'; | ||||||
| import InputCopyable from '@/components/InputCopyable.vue'; | import InputCopyable from '@/components/InputCopyable.vue'; | ||||||
| @ -19,6 +19,16 @@ function refreshSecret() { | |||||||
|   secret.value = generateSecret(); |   secret.value = generateSecret(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const counter = ref(0); | ||||||
|  | 
 | ||||||
|  | const [hotpValues] = computedRefreshable( | ||||||
|  |   () => | ||||||
|  |     Object.fromEntries( | ||||||
|  |       Array.from({ length: 10 }, (_, i) => [+counter.value + i, generateHOTP({ key: secret.value, counter: +counter.value + i })]), | ||||||
|  |     ), | ||||||
|  |   { throttle: 500 }, | ||||||
|  | ); | ||||||
|  | 
 | ||||||
| const [tokens] = computedRefreshable( | const [tokens] = computedRefreshable( | ||||||
|   () => ({ |   () => ({ | ||||||
|     previous: generateTOTP({ key: secret.value, now: now.value - 30000 }), |     previous: generateTOTP({ key: secret.value, now: now.value - 30000 }), | ||||||
| @ -68,23 +78,6 @@ const secretValidationRules = [ | |||||||
|         </c-tooltip> |         </c-tooltip> | ||||||
|       </template> |       </template> | ||||||
|     </c-input-text> |     </c-input-text> | ||||||
| 
 |  | ||||||
|     <div> |  | ||||||
|       <TokenDisplay :tokens="tokens" /> |  | ||||||
| 
 |  | ||||||
|       <n-progress :percentage="(100 * interval) / 30" :color="theme.primaryColor" :show-indicator="false" /> |  | ||||||
|       <div style="text-align: center"> |  | ||||||
|         Next in {{ String(Math.floor(30 - interval)).padStart(2, '0') }}s |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|     <div mt-4 flex flex-col items-center justify-center gap-3> |  | ||||||
|       <n-image :src="qrcode" /> |  | ||||||
|       <c-button :href="keyUri" target="_blank"> |  | ||||||
|         Open Key URI in new tab |  | ||||||
|       </c-button> |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
|   <div style="max-width: 350px"> |  | ||||||
|     <InputCopyable |     <InputCopyable | ||||||
|       label="Secret in hexadecimal" |       label="Secret in hexadecimal" | ||||||
|       :value="base32toHex(secret)" |       :value="base32toHex(secret)" | ||||||
| @ -93,11 +86,41 @@ const secretValidationRules = [ | |||||||
|       mb-5 |       mb-5 | ||||||
|     /> |     /> | ||||||
| 
 | 
 | ||||||
|  |     <div mt-4 flex flex-col items-center justify-center gap-3> | ||||||
|  |       <n-image :src="qrcode" /> | ||||||
|  |       <c-button :href="keyUri" target="_blank"> | ||||||
|  |         Open Key URI in new tab | ||||||
|  |       </c-button> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |   <div style="max-width: 350px"> | ||||||
|  |     <div> | ||||||
|  |       <c-input-text | ||||||
|  |         v-model:value="counter" | ||||||
|  |         label="Start-value for HOTP counter" | ||||||
|  |         placeholder="Start counter for HOTP at..." | ||||||
|  |         type="number" | ||||||
|  |         mb-5 | ||||||
|  |         mt-5 | ||||||
|  |       /> | ||||||
|  |       <InputCopyable | ||||||
|  |         v-for="(value, currentCounter) in hotpValues" :key="currentCounter" | ||||||
|  |         :value="value" | ||||||
|  |         readonly | ||||||
|  |         :label="`HOTP ${currentCounter}:`" | ||||||
|  |         label-position="left" | ||||||
|  |         label-width="90px" | ||||||
|  |         label-align="right" | ||||||
|  |         placeholder="HOTP will be displayed here" | ||||||
|  |         mb-1 | ||||||
|  |       /> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |   <div style="max-width: 350px"> | ||||||
|     <InputCopyable |     <InputCopyable | ||||||
|       label="Epoch" |       label="Epoch" | ||||||
|       :value="Math.floor(now / 1000).toString()" |       :value="Math.floor(now / 1000).toString()" | ||||||
|       readonly |       readonly | ||||||
|       mb-5 |  | ||||||
|       placeholder="Epoch in sec will be displayed here" |       placeholder="Epoch in sec will be displayed here" | ||||||
|     /> |     /> | ||||||
| 
 | 
 | ||||||
| @ -122,6 +145,15 @@ const secretValidationRules = [ | |||||||
|       label-align="right" |       label-align="right" | ||||||
|       label="Padded hex:" |       label="Padded hex:" | ||||||
|     /> |     /> | ||||||
|  | 
 | ||||||
|  |     <div> | ||||||
|  |       <TokenDisplay :tokens="tokens" mt-5 /> | ||||||
|  | 
 | ||||||
|  |       <n-progress :percentage="(100 * interval) / 30" :color="theme.primaryColor" :show-indicator="false" /> | ||||||
|  |       <div style="text-align: center"> | ||||||
|  |         Next in {{ String(Math.floor(30 - interval)).padStart(2, '0') }}s | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user