Skip to content
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
126 changes: 77 additions & 49 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "domrobot-client",
"version": "3.3.1",
"version": "4.0.0",
"description": "INWX Domrobot Node.JS Client",
"author": "INWX Developer <developer@inwx.de> (https://inwx.com)",
"main": "lib/index.js",
Expand Down Expand Up @@ -33,7 +33,7 @@
"test": "node --test"
},
"dependencies": {
"otplib": "^12.0.1"
"otplib": "^13.5.0"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
Expand All @@ -44,6 +44,6 @@
"typescript-eslint": "^8.71.0"
},
"engines": {
"node": ">=18"
"node": ">=24"
}
}
4 changes: 2 additions & 2 deletions src/domrobot.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as otplib from 'otplib';

export class ApiClient {
public static readonly CLIENT_VERSION = '3.3.1';
public static readonly CLIENT_VERSION = '4.0.0';

public static readonly API_URL_LIVE = 'https://api.domrobot.com/jsonrpc/';
public static readonly API_URL_OTE = 'https://api.ote.domrobot.com/jsonrpc/';
Expand Down Expand Up @@ -127,7 +127,7 @@ export class ApiClient {
if (sharedSecret === null) {
return Promise.reject('API requests two factor challenge but no shared secret is given. Aborting.');
}
const secretCode = otplib.authenticator.generate(sharedSecret);
const secretCode = otplib.generateSync({ secret: sharedSecret });
const unlockResult = await this.callApi('account.unlock', { tan: secretCode });
if (unlockResult.code !== 1000) {
return unlockResult;
Expand Down
16 changes: 9 additions & 7 deletions test/domrobot.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { version } = require('../package.json');

const originalFetch = globalThis.fetch;
const TFA_ERROR = 'API requests two factor challenge but no shared secret is given. Aborting.';
const SHARED_SECRET = 'JBSWY3DPEHPK3PXP';
const SHARED_SECRET = 'KVKFKRCPNZQUYMLXOVYDSQKJKZDTSRLD'; // 32 base32 characters, 20 bytes
const SET_COOKIE = 'domrobot=abc; Path=/; HttpOnly';

let calls = [];
Expand Down Expand Up @@ -260,13 +260,15 @@ describe('login()', () => {
});
}

// The behaviour of 3.3.0 and earlier: only null is a missing secret.
test('makes the unlock call for an empty secret and returns its response', async () => {
const unlockData = { code: 2200, msg: 'Authentication error' };
stubFetch(loginResponse({ code: 1000, resData: { tfa: 'GOOGLE-AUTH' } }), jsonResponse(unlockData));
// Since 4.0.0: otplib rejects an empty secret, and login() rejects with that error.
test('rejects with an Error and makes no unlock call for an empty secret', async () => {
stubFetch(loginResponse({ code: 1000, resData: { tfa: 'GOOGLE-AUTH' } }));

assert.deepEqual(await new ApiClient().login('user', 'secret', ''), unlockData);
assert.equal(calls[1].body.method, 'account.unlock');
await assert.rejects(new ApiClient().login('user', 'secret', ''), (reason) => {
assert.ok(reason instanceof Error);
return true;
});
assert.equal(calls.length, 1);
});
});

Expand Down
Loading