| 45 | }; |
| 46 | |
| 47 | export const generateTotpCode = ( |
| 48 | secret: string, |
| 49 | timestamp: number = Date.now(), |
| 50 | ): string => { |
| 51 | const key = decodeBase32(secret); |
| 52 | const counter = Math.floor(timestamp / 1_000 / 30); |
| 53 | const counterBuffer = Buffer.alloc(8); |
| 54 | const highCounter = Math.floor(counter / 2 ** 32); |
| 55 | const lowCounter = counter % 2 ** 32; |
| 56 | |
| 57 | counterBuffer.writeUInt32BE(highCounter, 0); |
| 58 | counterBuffer.writeUInt32BE(lowCounter, 4); |
| 59 | |
| 60 | const hmac = createHmac("sha1", key).update(counterBuffer).digest(); |
| 61 | const offset = hmac[hmac.length - 1]! % 16; |
| 62 | |
| 63 | const binaryCode = |
| 64 | (hmac[offset]! % 128) * 16_777_216 + |
| 65 | hmac[offset + 1]! * 65_536 + |
| 66 | hmac[offset + 2]! * 256 + |
| 67 | hmac[offset + 3]!; |
| 68 | |
| 69 | return (binaryCode % 1_000_000).toString().padStart(6, "0"); |
| 70 | }; |
| 71 | |
| 72 | /** |
| 73 | * If fewer than `bufferMs` remain in the current 30-second TOTP window, |