aboutsummaryrefslogtreecommitdiff
path: root/src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue
blob: fd9f173719a9679e757d6a82248101aa86bfe2e1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
<template>
  <div style="max-width: 350px">
    <n-form-item label="Secret" v-bind="secretValidationAttrs">
      <n-input v-model:value="secret" placeholder="Paste your TOTP secret...">
        <template #suffix>
          <n-tooltip trigger="hover">
            <template #trigger>
              <c-button circle variant="text" @click="refreshSecret">
                <n-icon :component="Refresh" />
              </c-button>
            </template>
            Generate secret token
          </n-tooltip>
        </template>
      </n-input>
    </n-form-item>

    <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>
    <n-space justify="center" vertical align="center" style="margin-top: 10px">
      <n-image :src="qrcode"></n-image>
      <c-button :href="keyUri" target="_blank">Open Key URI in new tab</c-button>
    </n-space>
  </div>
  <div style="max-width: 350px">
    <n-form-item label="Secret in hexadecimal">
      <input-copyable :value="base32toHex(secret)" readonly placeholder="Secret in hex will be displayed here" />
    </n-form-item>

    <n-form-item label="Epoch">
      <input-copyable
        :value="Math.floor(now / 1000).toString()"
        readonly
        placeholder="Epoch in sec will be displayed here"
      />
    </n-form-item>
    <n-form-item label="Iteration" :show-feedback="false">
      <n-input-group>
        <n-input-group-label style="width: 110px">Count:</n-input-group-label>
        <input-copyable
          :value="String(getCounterFromTime({ now, timeStep: 30 }))"
          readonly
          placeholder="Iteration count will be displayed here"
        />
      </n-input-group>
    </n-form-item>

    <n-form-item label="Iteration" :show-label="false" style="margin-top: 5px">
      <n-input-group>
        <n-input-group-label style="width: 110px">Padded hex:</n-input-group-label>
        <input-copyable
          :value="getCounterFromTime({ now, timeStep: 30 }).toString(16).padStart(16, '0')"
          readonly
          placeholder="Iteration count in hex will be displayed here"
        />
      </n-input-group>
    </n-form-item>
  </div>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue';
import { Refresh } from '@vicons/tabler';
import { useTimestamp } from '@vueuse/core';
import { useThemeVars } from 'naive-ui';
import { useStyleStore } from '@/stores/style.store';
import InputCopyable from '@/components/InputCopyable.vue';
import { useValidation } from '@/composable/validation';
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);
const theme = useThemeVars();
const styleStore = useStyleStore();

const secret = ref(generateSecret());

function refreshSecret() {
  secret.value = generateSecret();
}

const [tokens] = computedRefreshable(
  () => ({
    previous: generateTOTP({ key: secret.value, now: now.value - 30000 }),
    current: generateTOTP({ key: secret.value, now: now.value }),
    next: generateTOTP({ key: secret.value, now: now.value + 30000 }),
  }),
  { throttle: 500 },
);

const keyUri = computed(() => buildKeyUri({ secret: secret.value }));

const { qrcode } = useQRCode({
  text: keyUri,
  color: {
    background: computed(() => (styleStore.isDarkTheme ? '#ffffff' : '#00000000')),
    foreground: '#000000',
  },
  options: { width: 210 },
});

const { attrs: secretValidationAttrs } = useValidation({
  source: secret,
  rules: [
    {
      message: 'Secret should be a base32 string',
      validator: (value) => value.toUpperCase().match(/^[A-Z234567]+$/),
    },
    {
      message: 'Please set a secret',
      validator: (value) => value !== '',
    },
  ],
});
</script>

<style lang="less" scoped>
.n-progress {
  margin-top: 10px;
  ::v-deep(.n-progress-graph-line-fill) {
    transition-duration: 0.05s !important;
  }
}

.token {
  text-align: center;
  &.token-current {
    font-size: 20px;
  }
}
</style>
nly-snippets?h=dylan/github-api-option&follow=1'>bunjs-only-snippets/solid-dom-fixtures/customElements (unfollow)
AgeCommit message (Expand)AuthorFilesLines
2023-10-09fix(AbortSignal/fetch) fix AbortSignal.timeout, fetch lock behavior and fetch...Gravatar Ciro Spaciari 29-61/+303
2023-10-09Fix npm tag for canary bun-types, againGravatar Ashcon Partovi 2-56/+10
2023-10-09Add Fedora build instructions to development.md (#6359)Gravatar otterDeveloper 1-0/+10
2023-10-09added commands (#6314)Gravatar babar 1-1/+2
2023-10-09Update README.md (#6291)Gravatar TPLJ 1-1/+1
2023-10-09docs: fixing a couple typos (#6331)Gravatar Michael Di Prisco 2-2/+2
2023-10-09fix: support uint8 exit code range (#6303)Gravatar Liz 2-2/+11
2023-10-09Fix array variables preview in debugger (#6379)Gravatar 2hu 1-1/+4
2023-10-07feat(KeyObject) (#5940)Gravatar Ciro Spaciari 106-67/+9342
2023-10-07Exclude more filesGravatar Jarred Sumner 1-1/+1
2023-10-07Exclude more filesGravatar Jarred Sumner 1-1/+2
2023-10-07Update settings.jsonGravatar Jarred Sumner 1-1/+2
2023-10-07Update settings.jsonGravatar Jarred Sumner 1-2/+3
2023-10-06fix a couple install testsGravatar Dylan Conway 1-8/+8
2023-10-06formatGravatar Dylan Conway 1-1/+2
2023-10-06Fix memory leak in fetch() (#6350)Gravatar Jarred Sumner 1-2/+0
2023-10-06[types] allow onLoad plugin callbacks to return undefined (#6346)Gravatar Silver 1-1/+1
2023-10-06docs: `file.stream()` is not a promise (#6337)Gravatar Paul Nodet 1-1/+1