From 645d3d2bc91fb661e112278650eae51847016d3c Mon Sep 17 00:00:00 2001 From: "Foluso ADEBISI (DevBox Aux)" Date: Thu, 1 Oct 2026 10:35:02 +0100 Subject: [PATCH] fix(cropper): alias the queue's core domain to uc; show server error text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Saving failed with 400 'Unknown domain.' because queue items carry the legacy domain 'core', which the old bot mapped to 'uc' for a2loft URLs — my domain validation (added in the review pass) rejected it. The save now applies the same alias before validating and forwarding. Also: the client showed axios's generic 'Request failed with status code 400' because the catch destructured error.message; all cropper handlers now prefer the server's response.data.message (errMessage helper), so the real reason is visible. Verified: save with a 'core' item forwards to /identity/uc/profile/picture; a 400 shows 'Unknown domain.'; 46 tests. --- tools/cropper.js | 7 ++++-- tools/public/assets/cropper/app.js | 26 +++++++++++++--------- tools/test/cropper.test.js | 35 ++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/tools/cropper.js b/tools/cropper.js index 884c1a1..8bca1b4 100644 --- a/tools/cropper.js +++ b/tools/cropper.js @@ -12,6 +12,8 @@ import { stripPhonePrefix, cropPermission } from './permit.js'; const require = createRequire(import.meta.url); const QUEUE_NAME = 'id-images'; +/** Queue domains that differ from their a2loft code (legacy bot behaviour). */ +const DOMAIN_ALIASES = { core: 'uc' }; const BUCKET = 'files'; const REMOVEBG_URL = 'https://removebg.itcass.net/api/removebg'; @@ -348,13 +350,14 @@ export function createCropperRouter(cfg) { message: { upload_id, domain, agentDeviceHash } = {}, }, } = req.body; - if (!domainCodes.has(domain)) { + const urlDomain = DOMAIN_ALIASES[domain] || domain; + if (!domainCodes.has(urlDomain)) { return res.status(400).json({ ok: false, message: 'Unknown domain.' }); } const q = new URLSearchParams({ phone: req.session.phone, otp: req.query.otp }); try { const r = await fetchImpl( - `${base}/${encodeURIComponent(domain)}/profile/picture?${q.toString()}`, + `${base}/${encodeURIComponent(urlDomain)}/profile/picture?${q.toString()}`, { signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS), method: 'POST', diff --git a/tools/public/assets/cropper/app.js b/tools/public/assets/cropper/app.js index ab124e9..3b0f14a 100644 --- a/tools/public/assets/cropper/app.js +++ b/tools/public/assets/cropper/app.js @@ -95,6 +95,12 @@ Vue.config.ignoredElements = [/^cds-/]; }, methods: { + /** axios hides the server's reason in response.data — prefer it. */ + errMessage: function (error, fallback) { + return (error && error.response && error.response.data && error.response.data.message) || + (error && error.message) || fallback; + }, + toast: function (kind, title, caption) { var self = this; var id = ++toastSeq; @@ -157,8 +163,8 @@ Vue.config.ignoredElements = [/^cds-/]; ); this.agent = review.data.ok ? review.data.data : null; } - } catch ({ message }) { - this.toast('error', 'Operation Failed', message); + } catch (error) { + this.toast('error', 'Operation Failed', this.errMessage(error, 'Could not load the picture.')); } finally { this.busy.load = false; } @@ -186,8 +192,8 @@ Vue.config.ignoredElements = [/^cds-/]; var fr = new FileReader(); fr.onload = function () { self.cleanedImage = fr.result; }; fr.readAsDataURL(blob); - } catch ({ message }) { - this.toast('error', 'Image Cleanup Failed', message); + } catch (error) { + this.toast('error', 'Image Cleanup Failed', this.errMessage(error, 'Cleanup failed.')); } finally { this.busy.clean = false; } @@ -232,8 +238,8 @@ Vue.config.ignoredElements = [/^cds-/]; this.toast('success', 'Review Sent', 'Profile owner has been notified of the review.'); } this.reset(); - } catch ({ message }) { - this.toast('error', 'Operation Failed', message); + } catch (error) { + this.toast('error', 'Operation Failed', this.errMessage(error, 'Review failed.')); } finally { this.busy.review = false; } @@ -251,8 +257,8 @@ Vue.config.ignoredElements = [/^cds-/]; } this.toast('success', 'Removed!', data.message || ''); this.reset(); - } catch ({ message }) { - this.toast('error', 'Not Removed', message); + } catch (error) { + this.toast('error', 'Not Removed', this.errMessage(error, 'Removal failed.')); } finally { this.busy.review = false; } @@ -276,8 +282,8 @@ Vue.config.ignoredElements = [/^cds-/]; } this.toast('success', 'Saved', 'Picture saved for ' + this.message.message.caption); this.reset(); - } catch ({ message }) { - this.toast('error', 'Not Saved', message); + } catch (error) { + this.toast('error', 'Not Saved', this.errMessage(error, 'Save failed.')); } finally { this.busy.save = false; } diff --git a/tools/test/cropper.test.js b/tools/test/cropper.test.js index aa46422..ab10cca 100644 --- a/tools/test/cropper.test.js +++ b/tools/test/cropper.test.js @@ -511,3 +511,38 @@ test('picture fetches reject object keys outside the id-domains prefix', async ( await stop(); } }); + + +test('a queue domain of core is saved against the uc code (legacy alias)', async () => { + const legacy = JSON.stringify({ + upload_id: 'up-core', + file_name: 'id-domains/uc/raw-pictures/x/img.jpg', + caption: '08068287222', + domain: 'core', + raw_file_id: 'raw-1', + }); + const { base, a2loft, stop } = await boot({ queue: fakeQueue(legacy) }); + try { + const cookies = await login(base); + const res = await fetch(`${base}/cropper/profile/picture?otp=778899`, { + method: 'POST', + headers: { cookie: cookies, 'content-type': 'application/json' }, + body: JSON.stringify({ + pictureDataUrl: 'data:image/jpeg;base64,QUJD', + phone: '08068287222', + requiresCleaning: false, + message: { + pop_receipt: 'pop-1', + message_id: 'm-1', + message: { upload_id: 'up-core', domain: 'core', agentDeviceHash: 'a' }, + }, + }), + }); + const body = await res.json(); + assert.equal(res.status, 200, JSON.stringify(body)); + const save = a2loft.calls.find((c) => c.url.includes('/profile/picture')); + assert.equal(decodeURIComponent(new URL(save.url, 'http://x').pathname), '/identity/uc/profile/picture'); + } finally { + await stop(); + } +});