178 lines
6.8 KiB
JavaScript
178 lines
6.8 KiB
JavaScript
const { test } = require('node:test');
|
|
const assert = require('node:assert');
|
|
const { createDoodstreamOtpCoordinator, selectUploadAuth } = require('../lib/account-auth');
|
|
|
|
test('doodstream prefers the API key even when username/password are also set', () => {
|
|
const auth = selectUploadAuth('doodstream.com', {
|
|
apiKey: 'KEY123', username: 'u', password: 'p'
|
|
});
|
|
assert.deepEqual(auth, { apiKey: 'KEY123' }); // API path — no username leaks through
|
|
});
|
|
|
|
test('doodstream with only username/password uses web login (keyless fallback)', () => {
|
|
const auth = selectUploadAuth('doodstream.com', { username: 'u', password: 'p' });
|
|
assert.deepEqual(auth, { username: 'u', password: 'p' });
|
|
});
|
|
|
|
test('doodstream with empty apiKey + creds falls back to web login (no false API route)', () => {
|
|
const auth = selectUploadAuth('doodstream.com', { apiKey: '', username: 'u', password: 'p' });
|
|
assert.deepEqual(auth, { username: 'u', password: 'p' });
|
|
});
|
|
|
|
test('doodstream with nothing usable returns empty', () => {
|
|
assert.deepEqual(selectUploadAuth('doodstream.com', { apiKey: '', username: '', password: '' }), {});
|
|
});
|
|
|
|
test('voe.sx is unaffected by the doodstream special-case: username/password wins', () => {
|
|
// voe also supports both, but the empty-form bug is doodstream-specific; do
|
|
// not change voe routing.
|
|
const auth = selectUploadAuth('voe.sx', { apiKey: 'VKEY', username: 'u', password: 'p' });
|
|
assert.deepEqual(auth, { username: 'u', password: 'p' });
|
|
});
|
|
|
|
test('authType=api forces the API key for any hoster', () => {
|
|
assert.deepEqual(selectUploadAuth('voe.sx', { authType: 'api', apiKey: 'K', username: 'u', password: 'p' }), { apiKey: 'K' });
|
|
});
|
|
|
|
test('api-key-only account (no creds) uses the key', () => {
|
|
assert.deepEqual(selectUploadAuth('byse.sx', { apiKey: 'BKEY' }), { apiKey: 'BKEY' });
|
|
});
|
|
|
|
test('null / non-object account does not throw', () => {
|
|
assert.deepEqual(selectUploadAuth('doodstream.com', null), {});
|
|
assert.deepEqual(selectUploadAuth('doodstream.com', undefined), {});
|
|
});
|
|
|
|
function otpRequired(message = 'OTP erforderlich') {
|
|
const error = new Error(message);
|
|
error.otpRequired = true;
|
|
return error;
|
|
}
|
|
|
|
test('explicit web login is not overridden by a stored API key', () => {
|
|
assert.deepEqual(selectUploadAuth('doodstream.com', { authType: 'login', username: 'user', password: 'secret', apiKey: 'KEY' }), { username: 'user', password: 'secret' });
|
|
});
|
|
|
|
test('upload reuses the confirmed OTP session without another login', async () => {
|
|
let logins = 0;
|
|
const uploadSession = {};
|
|
const coordinator = createDoodstreamOtpCoordinator({
|
|
createUploader: () => ({ async login() { logins++; }, cloneSession: () => uploadSession })
|
|
});
|
|
const health = await coordinator.check({ username: 'user', password: 'secret' });
|
|
assert.equal(health.status, 'ok');
|
|
assert.equal(await coordinator.acquire({ username: 'user', password: 'secret' }), uploadSession);
|
|
assert.equal(logins, 1);
|
|
});
|
|
|
|
test('concurrent and repeated Doodstream checks request only one OTP', async () => {
|
|
let loginCalls = 0;
|
|
let releaseLogin;
|
|
const coordinator = createDoodstreamOtpCoordinator({
|
|
createUploader: () => ({
|
|
login: () => new Promise((resolve, reject) => {
|
|
loginCalls++;
|
|
releaseLogin = () => reject(otpRequired('OTP gesendet'));
|
|
})
|
|
})
|
|
});
|
|
const first = coordinator.check({ username: 'user', password: 'secret' });
|
|
const second = coordinator.check({ username: 'user', password: 'secret' });
|
|
assert.equal(loginCalls, 1);
|
|
releaseLogin();
|
|
assert.deepEqual(await first, { status: 'otp_required', message: 'OTP gesendet' });
|
|
assert.deepEqual(await second, { status: 'otp_required', message: 'OTP gesendet' });
|
|
assert.deepEqual(await coordinator.check({ username: 'user', password: 'secret' }), { status: 'otp_required', message: 'OTP gesendet' });
|
|
assert.equal(loginCalls, 1);
|
|
});
|
|
|
|
test('Doodstream OTP verification reuses the challenged uploader session', async () => {
|
|
let created = 0;
|
|
const calls = [];
|
|
const coordinator = createDoodstreamOtpCoordinator({
|
|
createUploader: () => {
|
|
created++;
|
|
return {
|
|
async login(_username, _password, otp) {
|
|
calls.push(otp || '');
|
|
if (!otp) throw otpRequired('OTP gesendet');
|
|
}
|
|
};
|
|
}
|
|
});
|
|
assert.equal((await coordinator.check({ username: 'user', password: 'secret' })).status, 'otp_required');
|
|
assert.deepEqual(await coordinator.check({ username: 'user', password: 'secret', otp: '123456' }), {
|
|
status: 'ok',
|
|
message: 'Login erfolgreich'
|
|
});
|
|
assert.equal(created, 1);
|
|
assert.deepEqual(calls, ['', '123456']);
|
|
});
|
|
|
|
test('a rejected OTP remains pending without requesting another code', async () => {
|
|
let created = 0;
|
|
let calls = 0;
|
|
const coordinator = createDoodstreamOtpCoordinator({
|
|
createUploader: () => {
|
|
created++;
|
|
return {
|
|
async login(_username, _password, otp) {
|
|
calls++;
|
|
if (!otp) throw otpRequired('OTP gesendet');
|
|
throw new Error('Code ungültig');
|
|
}
|
|
};
|
|
}
|
|
});
|
|
await coordinator.check({ username: 'user', password: 'secret' });
|
|
assert.deepEqual(await coordinator.check({ username: 'user', password: 'secret', otp: '000000' }), {
|
|
status: 'otp_required',
|
|
message: 'Code ungültig'
|
|
});
|
|
assert.deepEqual(await coordinator.check({ username: 'user', password: 'secret' }), {
|
|
status: 'otp_required',
|
|
message: 'Code ungültig'
|
|
});
|
|
assert.equal(created, 1);
|
|
assert.equal(calls, 2);
|
|
});
|
|
|
|
test('explicit OTP resend is rate-limited and starts one fresh session', async () => {
|
|
let currentTime = 1000;
|
|
let created = 0;
|
|
const coordinator = createDoodstreamOtpCoordinator({
|
|
now: () => currentTime,
|
|
resendCooldownMs: 60000,
|
|
createUploader: () => {
|
|
created++;
|
|
return { login: async () => { throw otpRequired('OTP gesendet'); } };
|
|
}
|
|
});
|
|
await coordinator.check({ username: 'user', password: 'secret' });
|
|
await coordinator.check({ username: 'user', password: 'secret', requestNewChallenge: true });
|
|
assert.equal(created, 1);
|
|
currentTime += 60000;
|
|
await coordinator.check({ username: 'user', password: 'secret', requestNewChallenge: true });
|
|
assert.equal(created, 2);
|
|
});
|
|
|
|
test('expired OTP submission cannot create a replacement challenge implicitly', async () => {
|
|
let currentTime = 1000;
|
|
let created = 0;
|
|
const coordinator = createDoodstreamOtpCoordinator({
|
|
now: () => currentTime,
|
|
challengeTtlMs: 1000,
|
|
createUploader: () => {
|
|
created++;
|
|
return { login: async () => { throw otpRequired('OTP gesendet'); } };
|
|
}
|
|
});
|
|
await coordinator.check({ username: 'user', password: 'secret' });
|
|
currentTime += 1001;
|
|
assert.deepEqual(await coordinator.check({ username: 'user', password: 'secret', otp: '123456' }), {
|
|
status: 'otp_required',
|
|
message: 'OTP-Anfrage ist abgelaufen. Bitte einen neuen Code anfordern.'
|
|
});
|
|
assert.equal(created, 1);
|
|
});
|