Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add helper to find next tick countdown #75

Merged
merged 1 commit into from
Jun 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
- [Difference between Authenticator and TOTP](#difference-between-authenticator-and-totp)
- [Base32 Keys and RFC3548](#base32-keys-and-rfc3548)
- [Displaying a QR code](#displaying-a-qr-code)
- [Getting Time Remaining / Time Used](#getting-time-remaining--time-used)
- [Exploring with local-repl](#exploring-with-local-repl)
- [Contributing](#contributing)
- [License](#license)
Expand Down Expand Up @@ -63,6 +64,7 @@ and includes additional methods to allow you to work with Google Authenticator.
- [Documentation][project-docs]
- [Demo][project-web]
- [FAQ / Common Issues](https://github.com/yeojz/otplib/wiki/FAQ)
- [List of available methods][type-ts-file] (documented via TypeScript)

## Installation

Expand Down Expand Up @@ -344,6 +346,16 @@ qrcode.toDataURL(otpauth, (err, imageUrl) => {
});
```

### Getting Time Remaining / Time Used

Helper methods for getting the remaining time and used time within a validity period
of a `totp` or `authenticator` token were introduced in `v10.0.0`.

```js
authenticator.timeUsed(); // or totp.timeUsed();
authenticator.timeRemaining(); // or totp.timeRemaining();
```

### Exploring with local-repl

If you'll like to explore the library with `local-repl` you can do so as well.
Expand Down Expand Up @@ -398,7 +410,8 @@ Check out: [CONTRIBUTING.md][pr-welcome-link]
[rfc-6238-wiki]: http://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm
[donate-badge]: https://img.shields.io/badge/donate-%3C3-red.svg?longCache=true&style=flat-square
[donate-link]: https://www.paypal.me/yeojz
[coffee-badge]: https://img.shields.io/badge/%E2%98%95%EF%B8%8F%20-buy%20me%20a%20coffee-orange.svg?longCache=true&style=flat-square
[coffee-badge]: https://img.shields.io/badge/%E2%98%95%EF%B8%8F-buy%20me%20a%20coffee-orange.svg?longCache=true&style=flat-square
[coffee-link]: https://ko-fi.com/geraldyeo
[type-ts-badge]: https://img.shields.io/badge/typedef-.d.ts-blue.svg?style=flat-square&longCache=true
[type-ts-link]: https://github.com/yeojz/otplib/tree/master/packages/types-ts
[type-ts-file]: https://github.com/yeojz/otplib/blob/master/packages/types-ts/index.d.ts
4 changes: 4 additions & 0 deletions packages/otplib-core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import totpCheckWithWindow from './totpCheckWithWindow';
import totpCounter from './totpCounter';
import totpOptions from './totpOptions';
import totpSecret from './totpSecret';
import totpTimeRemaining from './totpTimeRemaining';
import totpTimeUsed from './totpTimeUsed';
import totpToken from './totpToken';

/**
Expand Down Expand Up @@ -49,5 +51,7 @@ export {
totpCounter,
totpOptions,
totpSecret,
totpTimeRemaining,
totpTimeUsed,
totpToken
};
15 changes: 15 additions & 0 deletions packages/otplib-core/totpTimeRemaining.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import totpTimeUsed from './totpTimeUsed';

/**
* Calculates the number of seconds till next tick for TOTP
*
* @module otplib-core/totpTimeRemaining
* @param {number} epoch - starting time since the UNIX epoch (seconds)
* @param {number} step - time step (seconds)
* @return {number} - in seconds
*/
function timeRemaining(epoch, step) {
return step - totpTimeUsed(epoch, step);
}

export default timeRemaining;
18 changes: 18 additions & 0 deletions packages/otplib-core/totpTimeRemaining.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import totpTimeRemaining from './totpTimeRemaining';

describe('totpTimeRemaining', () => {
const time_30_00 = 1529154660000;
const time_30_10 = 1529154640000;

it('should return 0 ', () => {
expect(totpTimeRemaining(time_30_00, 30)).toBe(30);
});

it('should return 10 ', () => {
expect(totpTimeRemaining(time_30_10, 30)).toBe(20);
});

it('should not return 0 ', () => {
expect(totpTimeRemaining(time_30_00, 18)).toBe(6);
});
});
13 changes: 13 additions & 0 deletions packages/otplib-core/totpTimeUsed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Calculates the number of seconds used in the current tick for TOTP
*
* @module otplib-core/totpTimeUsed
* @param {number} epoch - starting time since the UNIX epoch (seconds)
* @param {number} step - time step (seconds)
* @return {number} - in seconds
*/
export function totpTimeUsed(epoch, step) {
return epoch % step;
}

export default totpTimeUsed;
18 changes: 18 additions & 0 deletions packages/otplib-core/totpTimeUsed.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import totpTimeUsed from './totpTimeUsed';

describe('totpTimeUsed', () => {
const time_30_00 = 1529154660000;
const time_30_10 = 1529154640000;

it('should return 0 ', () => {
expect(totpTimeUsed(time_30_00, 30)).toBe(0);
});

it('should return 10 ', () => {
expect(totpTimeUsed(time_30_10, 30)).toBe(10);
});

it('should not return 0 ', () => {
expect(totpTimeUsed(time_30_00, 18)).toBe(12);
});
});
30 changes: 29 additions & 1 deletion packages/otplib-totp/TOTP.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { totpCheckWithWindow, totpToken, totpOptions } from 'otplib-core';
import {
totpCheckWithWindow,
totpOptions,
totpTimeRemaining,
totpTimeUsed,
totpToken
} from 'otplib-core';
import hotp from 'otplib-hotp';

const HOTP = hotp.HOTP;
Expand Down Expand Up @@ -119,6 +125,28 @@ class TOTP extends HOTP {
}
return this.check(opts.token, opts.secret);
}

/**
* Calculates the number of seconds to before current token is invalid.
*
* @return {integer}
* @see {@link module:core/totpTimeRemaining}
*/
timeRemaining() {
const opt = this.optionsAll;
return totpTimeRemaining(opt.epoch, opt.step);
}

/**
* Calculates the number of seconds used for the current token validity period.
*
* @return {integer}
* @see {@link module:core/totpTimeUsed}
*/
timeUsed() {
const opt = this.optionsAll;
return totpTimeUsed(opt.epoch, opt.step);
}
}

TOTP.prototype.TOTP = TOTP;
Expand Down
8 changes: 8 additions & 0 deletions packages/otplib-totp/TOTP.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ describe('TOTP', () => {
expect(spy).toHaveBeenCalledWith('token', 'secret');
});

it('method: totpTimeRemaining', () => {
methodExpectation('timeRemaining', 'totpTimeRemaining');
});

it('method: totpTimeUsed', () => {
methodExpectation('timeUsed', 'totpTimeUsed');
});

function methodExpectation(methodName, coreName) {
jest.spyOn(core, coreName).mockImplementation(() => 'result');

Expand Down
8 changes: 8 additions & 0 deletions packages/types-ts/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ type totpOptions = (options: any) => totpOptionsInterface;

type totpSecret = createHmacSecret;

type totpTimeRemaining = (epoch: number, step: number) => number;

type totpTimeUsed = (epoch: number, step: number) => number;

type totpToken = (secret: string, options: totpOptionsInterface) => string;

declare class HOTP {
Expand All @@ -95,6 +99,8 @@ declare class TOTP extends HOTP {
check(token: string, secret: string): boolean;
checkDelta(token: string, secret: string): number | null;
verify(opts: totpVerifyOptionsInterface): boolean;
timeUsed(): number;
timeRemaining(): number;
}

declare class Authenticator extends TOTP {
Expand Down Expand Up @@ -143,5 +149,7 @@ declare module 'otplib/core' {
const totpCounter: totpCounter;
const totpOptions: totpOptions;
const totpSecret: totpSecret;
const totpTimeRemaining: totpTimeRemaining;
const totpTimeUsed: totpTimeUsed;
const totpToken: totpToken;
}
10 changes: 10 additions & 0 deletions packages/types-ts/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ otplib.authenticator.decode('test'); // $ExpectType string
otplib.authenticator.encode('test'); // $ExpectType string
otplib.authenticator.verify({ secret, token }); // $ExpectType boolean
otplib.authenticator.keyuri('me', 'otplib-test', secret); // $ExpectType string
otplib.authenticator.timeRemaining(); // $ExpectType number
otplib.authenticator.timeUsed(); // $ExpectType number
otplib.authenticator.Authenticator;
otplib.authenticator.getClass();
authenticator.check(token, secret); // $ExpectType boolean
Expand All @@ -25,18 +27,24 @@ authenticator.decode('test'); // $ExpectType string
authenticator.encode('test'); // $ExpectType string
authenticator.verify({ secret, token }); // $ExpectType boolean
authenticator.keyuri('me', 'otplib-test', secret); // $ExpectType string
authenticator.timeRemaining(); // $ExpectType number
authenticator.timeUsed(); // $ExpectType number
authenticator.Authenticator;
authenticator.getClass();

token = otplib.totp.generate(SECRET); // $ExpectType string
otplib.totp.check(token, SECRET); // $ExpectType boolean
otplib.totp.checkDelta(token, SECRET); // $ExpectType number | null
otplib.totp.verify({ secret: SECRET, token }); // $ExpectType boolean
otplib.totp.timeRemaining(); // $ExpectType number
otplib.totp.timeUsed(); // $ExpectType number
otplib.totp.TOTP;
otplib.totp.getClass();
totp.check(token, SECRET); // $ExpectType boolean
totp.checkDelta(token, SECRET); // $ExpectType number | null
totp.verify({ secret: SECRET, token }); // $ExpectType boolean
totp.timeRemaining(); // $ExpectType number
totp.timeUsed(); // $ExpectType number
totp.TOTP;
totp.getClass();

Expand All @@ -63,3 +71,5 @@ core.totpCheckWithWindow('123', SECRET, totpOpt); // $ExpectType number | null
core.totpCounter(new Date().valueOf(), 0); // $ExpectType number
core.totpSecret(SECRET, { algorithm: totpOpt.algorithm, encoding: totpOpt.encoding }); // $ExpectType Buffer
core.totpToken(SECRET, totpOpt); // $ExpectType string
core.totpTimeRemaining(1, 1); // $ExpectType number
core.totpTimeUsed(1, 1); // $ExpectType number
18 changes: 7 additions & 11 deletions site/public/app.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/* global QRCode otplib*/
(function() {
var secret = '';
var step = 30;
var timing;

otplib.authenticator.options = {
window: 1
window: 1,
step: 30
};

function toggleTabs(evt) {
Expand Down Expand Up @@ -46,23 +46,19 @@
document.querySelector('.otp-token').innerHTML = token;
}

function setTimeLeft(timeLeft) {
document.querySelector('.otp-countdown').innerHTML = timeLeft + 's';
}

function generator() {
if (!secret) {
window.clearInterval(timing);
return;
}

const epoch = Math.floor(new Date().getTime() / 1000);
const count = epoch % 30;

if (count === 0) {
if (otplib.authenticator.timeUsed() === 0) {
setToken(otplib.authenticator.generate(secret));
}
setTimeLeft(step - count);

// time left
document.querySelector('.otp-countdown').innerHTML =
otplib.authenticator.timeRemaining() + 's';
}

function startCountdown() {
Expand Down