mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-24 16:56:14 -04:00
chore(lint): switched to a better lint config
This commit is contained in:
parent
4d2b037dbe
commit
33c9b6643f
178 changed files with 4105 additions and 3371 deletions
|
@ -1,86 +1,13 @@
|
|||
<template>
|
||||
<div style="max-width: 350px">
|
||||
<c-input-text
|
||||
v-model:value="secret"
|
||||
label="Secret"
|
||||
placeholder="Paste your TOTP secret..."
|
||||
mb-5
|
||||
:validation-rules="secretValidationRules"
|
||||
>
|
||||
<template #suffix>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<c-button circle variant="text" size="small" @click="refreshSecret">
|
||||
<icon-mdi-refresh />
|
||||
</c-button>
|
||||
</template>
|
||||
Generate secret token
|
||||
</n-tooltip>
|
||||
</template>
|
||||
</c-input-text>
|
||||
|
||||
<div>
|
||||
<token-display :tokens="tokens" style="margin-top: 2px" />
|
||||
|
||||
<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"></n-image>
|
||||
<c-button :href="keyUri" target="_blank">Open Key URI in new tab</c-button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="max-width: 350px">
|
||||
<input-copyable
|
||||
label="Secret in hexadecimal"
|
||||
:value="base32toHex(secret)"
|
||||
readonly
|
||||
placeholder="Secret in hex will be displayed here"
|
||||
mb-5
|
||||
/>
|
||||
|
||||
<input-copyable
|
||||
label="Epoch"
|
||||
:value="Math.floor(now / 1000).toString()"
|
||||
readonly
|
||||
mb-5
|
||||
placeholder="Epoch in sec will be displayed here"
|
||||
/>
|
||||
|
||||
<p>Iteration</p>
|
||||
|
||||
<input-copyable
|
||||
:value="String(getCounterFromTime({ now, timeStep: 30 }))"
|
||||
readonly
|
||||
label="Count:"
|
||||
label-position="left"
|
||||
label-width="90px"
|
||||
label-align="right"
|
||||
placeholder="Iteration count will be displayed here"
|
||||
/>
|
||||
|
||||
<input-copyable
|
||||
:value="getCounterFromTime({ now, timeStep: 30 }).toString(16).padStart(16, '0')"
|
||||
readonly
|
||||
placeholder="Iteration count in hex will be displayed here"
|
||||
label-position="left"
|
||||
label-width="90px"
|
||||
label-align="right"
|
||||
label="Padded hex:"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { useTimestamp } from '@vueuse/core';
|
||||
import { useThemeVars } from 'naive-ui';
|
||||
import { useQRCode } from '../qr-code-generator/useQRCode';
|
||||
import { base32toHex, buildKeyUri, generateSecret, generateTOTP, getCounterFromTime } from './otp.service';
|
||||
import TokenDisplay from './token-display.vue';
|
||||
import { useStyleStore } from '@/stores/style.store';
|
||||
import InputCopyable from '@/components/InputCopyable.vue';
|
||||
import { computedRefreshable } from '@/composable/computedRefreshable';
|
||||
import { generateTOTP, buildKeyUri, generateSecret, base32toHex, getCounterFromTime } from './otp.service';
|
||||
import { useQRCode } from '../qr-code-generator/useQRCode';
|
||||
import TokenDisplay from './token-display.vue';
|
||||
|
||||
const now = useTimestamp();
|
||||
const interval = computed(() => (now.value / 1000) % 30);
|
||||
|
@ -125,6 +52,83 @@ const secretValidationRules = [
|
|||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="max-width: 350px">
|
||||
<c-input-text
|
||||
v-model:value="secret"
|
||||
label="Secret"
|
||||
placeholder="Paste your TOTP secret..."
|
||||
mb-5
|
||||
:validation-rules="secretValidationRules"
|
||||
>
|
||||
<template #suffix>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<c-button circle variant="text" size="small" @click="refreshSecret">
|
||||
<icon-mdi-refresh />
|
||||
</c-button>
|
||||
</template>
|
||||
Generate secret token
|
||||
</n-tooltip>
|
||||
</template>
|
||||
</c-input-text>
|
||||
|
||||
<div>
|
||||
<TokenDisplay :tokens="tokens" style="margin-top: 2px" />
|
||||
|
||||
<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
|
||||
label="Secret in hexadecimal"
|
||||
:value="base32toHex(secret)"
|
||||
readonly
|
||||
placeholder="Secret in hex will be displayed here"
|
||||
mb-5
|
||||
/>
|
||||
|
||||
<InputCopyable
|
||||
label="Epoch"
|
||||
:value="Math.floor(now / 1000).toString()"
|
||||
readonly
|
||||
mb-5
|
||||
placeholder="Epoch in sec will be displayed here"
|
||||
/>
|
||||
|
||||
<p>Iteration</p>
|
||||
|
||||
<InputCopyable
|
||||
:value="String(getCounterFromTime({ now, timeStep: 30 }))"
|
||||
readonly
|
||||
label="Count:"
|
||||
label-position="left"
|
||||
label-width="90px"
|
||||
label-align="right"
|
||||
placeholder="Iteration count will be displayed here"
|
||||
/>
|
||||
|
||||
<InputCopyable
|
||||
:value="getCounterFromTime({ now, timeStep: 30 }).toString(16).padStart(16, '0')"
|
||||
readonly
|
||||
placeholder="Iteration count in hex will be displayed here"
|
||||
label-position="left"
|
||||
label-width="90px"
|
||||
label-align="right"
|
||||
label="Padded hex:"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.n-progress {
|
||||
margin-top: 10px;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test.describe('Tool - OTP code generator', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
|
@ -19,7 +19,7 @@ test.describe('Tool - OTP code generator', () => {
|
|||
|
||||
test('OTP a generated from the provided secret', async ({ page }) => {
|
||||
page.evaluate(() => {
|
||||
Date.now = () => 1609477200000; //Jan 1, 2021
|
||||
Date.now = () => 1609477200000; // Jan 1, 2021
|
||||
});
|
||||
|
||||
await page.getByPlaceholder('Paste your TOTP secret...').fill('ITTOOLS');
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
base32toHex,
|
||||
buildKeyUri,
|
||||
generateHOTP,
|
||||
generateTOTP,
|
||||
hexToBytes,
|
||||
verifyHOTP,
|
||||
generateTOTP,
|
||||
verifyTOTP,
|
||||
buildKeyUri,
|
||||
base32toHex,
|
||||
} from './otp.service';
|
||||
|
||||
describe('otp functions', () => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { enc, HmacSHA1 } from 'crypto-js';
|
||||
import { HmacSHA1, enc } from 'crypto-js';
|
||||
import _ from 'lodash';
|
||||
import { createToken } from '../token-generator/token-generator.service';
|
||||
|
||||
|
@ -15,7 +15,7 @@ export {
|
|||
};
|
||||
|
||||
function hexToBytes(hex: string) {
|
||||
return (hex.match(/.{1,2}/g) ?? []).map((char) => parseInt(char, 16));
|
||||
return (hex.match(/.{1,2}/g) ?? []).map(char => parseInt(char, 16));
|
||||
}
|
||||
|
||||
function computeHMACSha1(message: string, key: string) {
|
||||
|
@ -29,10 +29,10 @@ function base32toHex(base32: string) {
|
|||
.toUpperCase() // Since base 32, we coerce lowercase to uppercase
|
||||
.replace(/=+$/, '')
|
||||
.split('')
|
||||
.map((value) => base32Chars.indexOf(value).toString(2).padStart(5, '0'))
|
||||
.map(value => base32Chars.indexOf(value).toString(2).padStart(5, '0'))
|
||||
.join('');
|
||||
|
||||
const hex = (bits.match(/.{1,8}/g) ?? []).map((chunk) => parseInt(chunk, 2).toString(16).padStart(2, '0')).join('');
|
||||
const hex = (bits.match(/.{1,8}/g) ?? []).map(chunk => parseInt(chunk, 2).toString(16).padStart(2, '0')).join('');
|
||||
|
||||
return hex;
|
||||
}
|
||||
|
@ -45,12 +45,12 @@ function generateHOTP({ key, counter = 0 }: { key: string; counter?: number }) {
|
|||
const bytes = hexToBytes(digest);
|
||||
|
||||
// Truncate
|
||||
const offset = bytes[19] & 0xf;
|
||||
const v =
|
||||
((bytes[offset] & 0x7f) << 24) |
|
||||
((bytes[offset + 1] & 0xff) << 16) |
|
||||
((bytes[offset + 2] & 0xff) << 8) |
|
||||
(bytes[offset + 3] & 0xff);
|
||||
const offset = bytes[19] & 0xF;
|
||||
const v
|
||||
= ((bytes[offset] & 0x7F) << 24)
|
||||
| ((bytes[offset + 1] & 0xFF) << 16)
|
||||
| ((bytes[offset + 2] & 0xFF) << 8)
|
||||
| (bytes[offset + 3] & 0xFF);
|
||||
|
||||
const code = String(v % 1000000).padStart(6, '0');
|
||||
|
||||
|
@ -63,10 +63,10 @@ function verifyHOTP({
|
|||
window = 0,
|
||||
counter = 0,
|
||||
}: {
|
||||
token: string;
|
||||
key: string;
|
||||
window?: number;
|
||||
counter?: number;
|
||||
token: string
|
||||
key: string
|
||||
window?: number
|
||||
counter?: number
|
||||
}) {
|
||||
for (let i = counter - window; i <= counter + window; ++i) {
|
||||
if (generateHOTP({ key, counter: i }) === token) {
|
||||
|
@ -94,11 +94,11 @@ function verifyTOTP({
|
|||
now = Date.now(),
|
||||
timeStep = 30,
|
||||
}: {
|
||||
token: string;
|
||||
key: string;
|
||||
window?: number;
|
||||
now?: number;
|
||||
timeStep?: number;
|
||||
token: string
|
||||
key: string
|
||||
window?: number
|
||||
now?: number
|
||||
timeStep?: number
|
||||
}) {
|
||||
const counter = getCounterFromTime({ now, timeStep });
|
||||
|
||||
|
@ -113,12 +113,12 @@ function buildKeyUri({
|
|||
digits = 6,
|
||||
period = 30,
|
||||
}: {
|
||||
secret: string;
|
||||
app?: string;
|
||||
account?: string;
|
||||
algorithm?: string;
|
||||
digits?: number;
|
||||
period?: number;
|
||||
secret: string
|
||||
app?: string
|
||||
account?: string
|
||||
algorithm?: string
|
||||
digits?: number
|
||||
period?: number
|
||||
}) {
|
||||
const params = {
|
||||
issuer: app,
|
||||
|
|
|
@ -1,9 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { toRefs } from 'vue';
|
||||
|
||||
const props = defineProps<{ tokens: { previous: string; current: string; next: string } }>();
|
||||
const { copy: copyPrevious, copied: previousCopied } = useClipboard();
|
||||
const { copy: copyCurrent, copied: currentCopied } = useClipboard();
|
||||
const { copy: copyNext, copied: nextCopied } = useClipboard();
|
||||
|
||||
const { tokens } = toRefs(props);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="labels" w-full flex items-center>
|
||||
<div flex-1 text-left>Previous</div>
|
||||
<div flex-1 text-center>Current OTP</div>
|
||||
<div flex-1 text-right>Next</div>
|
||||
<div flex-1 text-left>
|
||||
Previous
|
||||
</div>
|
||||
<div flex-1 text-center>
|
||||
Current OTP
|
||||
</div>
|
||||
<div flex-1 text-right>
|
||||
Next
|
||||
</div>
|
||||
</div>
|
||||
<n-input-group>
|
||||
<n-tooltip trigger="hover" placement="bottom">
|
||||
|
@ -29,9 +47,11 @@
|
|||
</n-tooltip>
|
||||
<n-tooltip trigger="hover" placement="bottom">
|
||||
<template #trigger>
|
||||
<c-button important:h-12 data-test-id="next-otp" @click.prevent="copyNext(tokens.next)">{{
|
||||
tokens.next
|
||||
}}</c-button>
|
||||
<c-button important:h-12 data-test-id="next-otp" @click.prevent="copyNext(tokens.next)">
|
||||
{{
|
||||
tokens.next
|
||||
}}
|
||||
</c-button>
|
||||
</template>
|
||||
<div>{{ nextCopied ? 'Copied !' : 'Copy next OTP' }}</div>
|
||||
</n-tooltip>
|
||||
|
@ -39,18 +59,6 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { toRefs } from 'vue';
|
||||
|
||||
const { copy: copyPrevious, copied: previousCopied } = useClipboard();
|
||||
const { copy: copyCurrent, copied: currentCopied } = useClipboard();
|
||||
const { copy: copyNext, copied: nextCopied } = useClipboard();
|
||||
|
||||
const props = defineProps<{ tokens: { previous: string; current: string; next: string } }>();
|
||||
const { tokens } = toRefs(props);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.current-otp {
|
||||
font-size: 22px;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue