Files
Snowflakey/lib/Token/otp.ts
2019-08-05 19:26:05 +02:00

62 lines
1.9 KiB
TypeScript

import Base32 from './base32'
import { createHmac, randomBytes } from 'crypto'
export default class OTP {
public seed: string;
constructor(seed: string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz!@#$-') {
this.seed = seed;
}
validateHotp(token: string, secret: any, counter: any): boolean {
const counterInt: number = parseInt(counter, 10) || 0;
if (!secret || !token || token.length !== 6 || isNaN(parseInt(token, 10))) return false;
return this._computeHotp(secret, counterInt) === token;
}
validateTotp(token: string, secret: string): boolean {
return this.validateHotp(token, secret, Math.floor(Date.now() / 30 / 1000));
}
generateKey(name: string = 'Secret Key', issuer: string = null, hotp: boolean = false): object {
const bytes: any = randomBytes(32);
const string: string[] = Array(32)
.fill(i => this.seed[Math.floor(bytes[i] / 255.0 * (this.seed.length - 1))])
.map((f, i) => f(i))
const key = {
raw: string.join(''),
base32: Base32.encode(string)
}
return Object.defineProperty(key, 'url', {
value: 'google_url',
writable: false,
get: () => `otpauth://${hotp ? 'h' : 't'}otp/${name}?secret=${key.base32}${issuer ? `&issuer=${issuer}` : ''}`
});
}
private _computeHotp(secret: any, counter: number): string {
secret = Base32.decode(secret);
let tmp: number = counter;
const chunks: Buffer = Buffer.alloc(8);
for (let i = 0; i < 8; i++) {
chunks[7 - i] = tmp & 0xff;
tmp = tmp >> 8;
}
const digest: Buffer = createHmac('sha1', secret).update(chunks).digest();
const offset: number = digest[digest.length - 1] & 0xf;
let code: number | string | number[] = (digest[offset] & 0x7f) << 24 |
(digest[offset + 1] & 0xff) << 16 |
(digest[offset + 2] & 0xff) << 8 |
(digest[offset + 3] & 0xff);
code = new Array(7).join('0') + code.toString(10);
return code.substr(-6);
}
}