Commit 0bd9a8c

Ansari <ansari_official@yahoo.com>
2024-06-16 00:25:09
002
1 parent 9f24a6f
002.TOTP/index.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>002.TOTP</title>
+    <link rel="stylesheet" href="./style.css">
+    <link rel="stylesheet" href="../assets/common.css">
+    <link rel="shortcut icon" href="../assets/favicon.png" type="image/png">
+</head>
+
+<body>
+    <!-- Modal Section -->
+    <input class="modal-state" id="modal" type="checkbox" />
+    <div class="modal">
+        <label class="modal__bg" for="modal"></label>
+        <div class="modal__inner" style="width:25%;">
+            <label class="modal__close" for="modal"></label>
+            <h2>002.TOTP</h2>
+            <p class="modal__p">
+            <ul>
+                <li style="margin-bottom:10px">
+                    TOTP works by taking a <span style="color:rgb(212, 175, 55)">secret</span> and a <span
+                        style="color:rgb(212, 175, 55)">timestamp</span> and hashing them together.
+                </li>
+                <li style="margin-bottom:10px">
+                    The <span style="color:rgb(212, 175, 55)">hash</span> is then truncated to a certain number of
+                    digits.
+                </li>
+                <li style="margin-bottom:10px">
+                    The digits are then used to generate a <span style="color:rgb(212, 175, 55)">one-time
+                        password</span>.
+                </li>
+                <li style="margin-bottom:10px">
+                    The <span style="color:rgb(212, 175, 55)">period</span> is the time in seconds that the <span
+                        style="color:rgb(212, 175, 55)">one-time password</span> is valid.
+                </li>
+                <li>
+                    Refer: <a href="https://tools.ietf.org/html/rfc6238" target="_blank"
+                        style="color:rgb(212, 175, 55)">RFC
+                        6238</a>.
+                </li>
+            </ul>
+
+            </p>
+        </div>
+    </div>
+
+    <!-- Main Content -->
+    <div class="container">
+        <div class="group">
+            <label for="secret">Secret:</label>
+            <input type="text" id="secret" name="secret" value="JV4SA43BMZSQ">
+        </div>
+        <div class="group">
+            <label for="period">Period:</label>
+            <input type="number" id="period" name="period" value="30" min="3">
+        </div>
+        <div class="group">
+            <label for="digits">Digits:</label>
+            <input type="number" id="digits" name="digits" value="6" min="1" max="10">
+        </div>
+        <p id="remaining"></p>
+        <p id="otp"></p>
+        <svg class="dial">
+            <circle r="230" cx="120" cy="140" fill="transparent" stroke="rgb(212, 175, 55,0.7)" stroke-width="10"
+                transform="rotate(-90 100 100)" stroke-dasharray="1570" stroke-dashoffset="0"></circle>
+        </svg>
+    </div>
+
+    <!-- Footer Section -->
+    <div class="footer">
+        <label for="modal">(i)</label>
+        <a class="arrow" href="../"></a>
+    </div>
+</body>
+<script src="./script.js"></script>
+
+</html>
\ No newline at end of file
002.TOTP/script.js
@@ -0,0 +1,120 @@
+const secret = document.getElementById('secret');
+const period = document.getElementById('period');
+const digits = document.getElementById('digits');
+const circle = document.querySelector("circle");
+const perimeter = circle.getAttribute("r") * 2 * Math.PI;
+
+function base32ToBytes(base32) {
+    const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+    let bits = '';
+    for (const char of base32) {
+        const val = base32Chars.indexOf(char.toUpperCase());
+        if (val === -1) {
+            throw new Error("Invalid Base32 character");
+        }
+        bits += val.toString(2).padStart(5, '0');
+    }
+    const bytes = [];
+    for (let i = 0; i < bits.length; i += 8) {
+        const byte = bits.substring(i, i + 8);
+        if (byte.length === 8) {
+            bytes.push(parseInt(byte, 2));
+        }
+    }
+    return new Uint8Array(bytes);
+}
+
+// Helper function to convert integer to byte array
+function intToBytes(num) {
+    const bytes = new Uint8Array(8);
+    for (let i = 7; i >= 0; i--) {
+        bytes[i] = num & 0xff;
+        num = num >> 8;
+    }
+    return bytes;
+}
+
+// HMAC-SHA1 function using Web Crypto API
+async function hmacSha1(key, message) {
+    const cryptoKey = await crypto.subtle.importKey(
+        'raw',
+        key,
+        { name: 'HMAC', hash: 'SHA-1' },
+        false,
+        ['sign']
+    );
+    const signature = await crypto.subtle.sign('HMAC', cryptoKey, message);
+    return new Uint8Array(signature);
+}
+
+async function generateTOTP({
+    seed,
+    timePeriod,
+    length
+}) {
+    const key = base32ToBytes(seed);
+    const epoch = Math.floor(Date.now() / 1000);
+    const time = Math.floor(epoch / timePeriod);
+    const timeBytes = intToBytes(time);
+    const hmac = await hmacSha1(key, timeBytes);
+
+    const offset = hmac[hmac.length - 1] & 0x0f;
+    const binary = (
+        ((hmac[offset] & 0x7f) << 24) |
+        ((hmac[offset + 1] & 0xff) << 16) |
+        ((hmac[offset + 2] & 0xff) << 8) |
+        (hmac[offset + 3] & 0xff)
+    );
+
+    const otp = binary % 10 ** length;
+    return otp.toString().padStart(length, '0');
+}
+
+async function setTOTP() {
+    const timeRemaining = (period.value - (Date.now() / 1000) % period.value).toFixed(0);
+    if (timeRemaining <= 0) {
+        return;
+    }
+    const otp = await generateTOTP({
+        seed: secret.value,
+        timePeriod: period.value,
+        length: digits.value
+    });
+    document.getElementById('otp').innerText = otp;
+    document.getElementBy
+}
+
+secret.addEventListener('input', async (e) => {
+    e.target.value = e.target.value.toUpperCase();
+    await setTOTP();
+});
+
+period.addEventListener('input', async (e) => {
+    if (e.target.value < 3) {
+        e.target.value = 3;
+    }
+    await setTOTP();
+});
+
+digits.addEventListener('input', async (e) => {
+    if (e.target.value < 1) {
+        e.target.value = 1;
+    }
+    if (e.target.value > 10) {
+        e.target.value = 10;
+    }
+    await setTOTP();
+});
+
+function updateCircle() {
+    const timeRemaining = (period.value - (Date.now() / 1000) % period.value).toFixed(0);
+    circle.setAttribute(
+        "stroke-dashoffset",
+        (perimeter * timeRemaining) / period.value - perimeter
+    );
+    document.getElementById("remaining").innerText = `Remaining: ${timeRemaining}s`;
+}
+
+updateCircle()
+setTOTP()
+setInterval(() => { updateCircle(); setTOTP() }, 1000);
\ No newline at end of file
002.TOTP/style.css
@@ -0,0 +1,39 @@
+.container {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    position: relative;
+    height: fit-content;
+    z-index: 10;
+}
+
+.group{
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: center;
+    width: 300px;
+    font-size: 20px;
+}
+
+#otp{
+    font-weight: 500;
+    font-style: normal;
+    font-size: 46px;
+    letter-spacing: 0.3em;
+    width: 100%;
+    text-align: center;
+    margin-top: 20px;
+}
+
+#remaining{
+    margin-top: 20px;
+}
+
+.dial{
+    position:absolute;
+    overflow: visible;
+    z-index: 1;
+    pointer-events: none;
+}
index.html
@@ -14,6 +14,7 @@
     </h1>
     <div class="flex flex-col text-white gap-x-[200px] gap-y-2 p-10">
         <a href="./001.Vision/" class="opacity-75 hover:opacity-100 hover:text-yellow-400">001.Vision</a>
+        <a href="./002.TOTP/" class="opacity-75 hover:opacity-100 hover:text-yellow-400" >002.TOTP</a>
     </div>
 </body>
 </html>
\ No newline at end of file