Skip to content

Instantly share code, notes, and snippets.

@donchev7
Created May 6, 2024 19:19
Show Gist options
  • Save donchev7/0c8113d1056f0774e059e0f788cfb335 to your computer and use it in GitHub Desktop.
Save donchev7/0c8113d1056f0774e059e0f788cfb335 to your computer and use it in GitHub Desktop.
Totp implementation for cloudflare workers
declare global {
interface SubtleCrypto {
timingSafeEqual(a: ArrayBuffer, b: ArrayBuffer): boolean // cloudflare workers has a timingSafeEqual method
}
}
export const generateTOTP = async (secret: string): Promise<string> => {
const time = Math.floor(Date.now() / 1000) // get the current time in seconds
const timeWindow = 15 * 60 // 15-minute time window
const timeStep = Math.floor(time / timeWindow) // calculate the time step
const counter = new Uint8Array(8) // counter as an 8-byte array
counter[7] = timeStep & 0xff // populate the least significant byte of the counter
counter[6] = (timeStep >> 8) & 0xff
counter[5] = (timeStep >> 16) & 0xff
counter[4] = (timeStep >> 24) & 0xff
const hmacKey = await crypto.subtle.importKey(
'raw', // format of the secret key
new TextEncoder().encode(secret), // the secret key as a byte array
{ name: 'HMAC', hash: { name: 'SHA-256' } }, // algorithm to use for HMAC
false, // not extractable
['sign'] // only need the key for signing
)
const hmacResult = await crypto.subtle.sign(
'HMAC', // HMAC algorithm
hmacKey,
counter // the counter as data to sign
)
const hmacArray = new Uint8Array(hmacResult) // convert the result to a byte array
const offset = hmacArray[hmacArray.length - 1] & 0xf // get the offset
const code =
((hmacArray[offset] & 0x7f) << 24) |
((hmacArray[offset + 1] & 0xff) << 16) |
((hmacArray[offset + 2] & 0xff) << 8) |
(hmacArray[offset + 3] & 0xff) // calculate the code
// format the code as a 6-digit string
return (code % 10 ** 6).toString().padStart(6, '0')
}
export const verifyTOTP = async (secret: string, token: string): Promise<boolean> => {
const otp = await generateTOTP(secret) // generate the expected OTP
return crypto.subtle.timingSafeEqual(new TextEncoder().encode(otp), new TextEncoder().encode(token))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment