Skip to content

Commit

Permalink
Merge pull request #25 from yeojz/feature/time-window
Browse files Browse the repository at this point in the history
FEAT: added time window function
  • Loading branch information
yeojz committed Feb 27, 2018
2 parents 7492aac + 226adf6 commit 51caae5
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 5 deletions.
3 changes: 3 additions & 0 deletions packages/otplib-core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import hotpOptions from './hotpOptions';
import hotpSecret from './hotpSecret';
import hotpToken from './hotpToken';
import totpCheck from './totpCheck';
import totpCheckWithWindow from './totpCheckWithWindow';
import totpCounter from './totpCounter';
import totpOptions from './totpOptions';
import totpSecret from './totpSecret';
Expand All @@ -25,6 +26,7 @@ import totpToken from './totpToken';
* hotpToken
*
* totpCheck
* totpCheckWithWindow
* totpCounter
* totpOptions
* totpSecret
Expand All @@ -43,6 +45,7 @@ export {
hotpSecret,
hotpToken,
totpCheck,
totpCheckWithWindow,
totpCounter,
totpOptions,
totpSecret,
Expand Down
2 changes: 1 addition & 1 deletion packages/otplib-core/totpCheck.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import totpToken from './totpToken';
* @param {object} options - options which was used to generate it originally
* @return {boolean}
*/
function totpCheck(token, secret, options = {}){
function totpCheck(token, secret, options){
const systemToken = totpToken(secret, options || {});

if (systemToken.length < 1) {
Expand Down
34 changes: 34 additions & 0 deletions packages/otplib-core/totpCheckWithWindow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import totpCheck from './totpCheck';

function getPrevWindowOption(options, windowCount) {
return Object.assign(options, {
epoch: options.epoch - (options.step * windowCount)
});
}

/**
* Checks the provided OTP token against system generated token
* with support for checking previous x time-step windows
*
* @module otplib-core/totpCheck
* @param {string} token - the OTP token to check
* @param {string} secret - your secret that is used to generate the token
* @param {object} options - options which was used to generate it originally
* @return {boolean}
*/
function totpCheckWithWindow(token, secret, options) {
let opt = Object.assign({}, options);
const rounds = Math.floor(opt.window || 0) + 1;

for (let i = 0; i < rounds; i++) {
opt = getPrevWindowOption(opt, i);

if (totpCheck(token, secret, opt)) {
return true;
}
}

return false;
}

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

jest.mock('./totpCheck', () => jest.fn());
const {default: totpCheckOriginal} = require.requireActual('./totpCheck');

describe('totpCheck', function() {

const secret = 'i6im0gc96j0mn00c';

const timeToken = [
{
time: 1519050965,
token: 737086
},
{
time: 1519050992,
token: 555283
},
{
time: 1519051024,
token: 712564
}
];

function token(n) {
return timeToken[n].token;
}

function time(n) {
return timeToken[n].time;
}

it('should call totpCheck 1 time when window is 0', function() {
totpCheck.mockImplementation(() => false);

totpCheckWithWindow(token(0), secret, {
epoch: time(0),
step: 30,
window: 0
});

expect(totpCheck).toHaveBeenCalledTimes(1);
});

it('should call totpCheck 2 times when window is 1', function() {
totpCheck.mockImplementation(() => false);

totpCheckWithWindow('', secret, {
epoch: time(1),
step: 30,
window: 1
});

expect(totpCheck).toHaveBeenCalledTimes(2);
});

it('current 3, window 1, token 0, called 2, return false', function() {
totpCheck.mockImplementation((...args) => {
return totpCheckOriginal(...args);
})

const opt = {
crypto,
epoch: time(2),
step: 30,
window: 1
};

const result = totpCheckWithWindow(token(0), secret, opt);

expect(result).toBe(false);
expect(totpCheck).toHaveBeenCalledTimes(2);
});

it('current 2, window 1, token 1, called 2, return true', function() {
totpCheck.mockImplementation((...args) => {
return totpCheckOriginal(...args);
})

const opt = {
crypto,
epoch: time(1),
step: 30,
window: 1
};

const result = totpCheckWithWindow(token(0), secret, opt);

expect(result).toBe(true);
expect(totpCheck).toHaveBeenCalledTimes(2);
});

it('current 3, window 2, token 1, called 2, return true', function() {
totpCheck.mockImplementation((...args) => {
return totpCheckOriginal(...args);
})

const opt = {
crypto,
epoch: time(2),
step: 30,
window: 2
};

const result = totpCheckWithWindow(token(1), secret, opt);

expect(result).toBe(true);
expect(totpCheck).toHaveBeenCalledTimes(2);
});
});
6 changes: 5 additions & 1 deletion packages/otplib-core/totpOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import totpSecret from './totpSecret';
const defaultOptions = {
createHmacSecret: totpSecret,
epoch: null,
step: 30
step: 30,
window: 0
};

/**
Expand All @@ -14,6 +15,7 @@ const defaultOptions = {
* @param {number} options.digits - the output token length
* @param {string} options.epoch - starting time since the UNIX epoch (seconds)
* @param {number} options.step - time step (seconds)
* @param {number} options.window - acceptable window where codes a valid. Will be rounded down to nearest integer
* @return {object}
*/
function totpOptions(options = {}) {
Expand All @@ -23,6 +25,8 @@ function totpOptions(options = {}) {
options
);

opt.window = Math.floor(opt.window || 0);

opt.epoch = typeof opt.epoch === 'number'
? opt.epoch * 1000
: Date.now();
Expand Down
27 changes: 24 additions & 3 deletions packages/otplib-core/totpOptions.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ describe('totpOptions', function () {
digits: 6,
encoding: 'ascii',
epoch: 1483228800000,
step: 30
step: 30,
window: 0
};

const epoch = {
epoch: defaults.epoch * 1000
};

beforeEach(function () {
Expand All @@ -28,13 +33,29 @@ describe('totpOptions', function () {
expect(totpOptions(void 0)).toEqual(defaults);
});

it('should return javascript epoch', function () {
const opt = Object.assign({}, defaults);
const expected = Object.assign({}, opt, epoch);
expect(totpOptions(opt)).toEqual(expected);
});

it('should return options with new values added', function () {
const opt = Object.assign({}, defaults, {
extra: true
});

const expected = Object.assign({}, defaults, opt, {
epoch: defaults.epoch * 1000
const expected = Object.assign({}, opt, epoch);

expect(totpOptions(opt)).toEqual(expected);
});

it('should return window with rounded down number', function () {
const opt = Object.assign({}, defaults, {
window: 1.5
});

const expected = Object.assign({}, opt, epoch, {
window: 1
});

expect(totpOptions(opt)).toEqual(expected);
Expand Down

0 comments on commit 51caae5

Please sign in to comment.