feat(erli): reuse an existing Allegro connection's credentials for category access (#1387) - #1405
Conversation
…tegory access (#1387) Extends the #1384 Erli credentials panel with a reuse-vs-manual choice: when the operator already has an Allegro connection, its clientId/clientSecret can be copied server-side into the Erli connection instead of registering a second Allegro app. The raw clientSecret never round-trips through the browser. The credential-copy logic is dispatched through a new, platform-neutral ConnectionCredentialsRewriterPort + registry (mirroring the existing ConnectionConfigShapeValidatorPort/ConnectionCredentialsShapeValidatorPort pair) so the generic ConnectionService.updateCredentials stays unaware that Allegro or Erli exist. The Erli-specific resolution lives entirely in ErliAllegroCredentialsRewriterAdapter, registered from a companion ErliCredentialsRewriterModule (mirrors ErliWebhookProvisioningModule) since it needs ConnectionPort, which is intentionally outside the plugin-neutral HostServices bag. Closes #1387 Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
ReviewOverall this is a clean, well-precedented change: pulling the reuse-resolution logic out of IMPORTANT
SUGGESTION
Security No security findings. The design intentionally keeps the raw secret server-side (never echoed in responses, error messages only leak the already-known connection ID/platformType), the authorization boundary ( Verdict: approve with changes - resolve or explicitly document #1 and #2 before merge; #3/#4 are optional follow-ups. Naming, layering, DI tokens, and test coverage all follow the documented conventions. |
piotrswierzy
left a comment
There was a problem hiding this comment.
/pr-review — feat(erli): reuse an existing Allegro connection's credentials for category access (#1387)
Summary
Adds a "reuse an existing Allegro connection's app credentials" path to the Erli credentials panel, resolving the raw clientSecret entirely server-side so it never reaches the browser or any response body. The standout is the architecture: an initial version that embedded 'allegro'/allegroClient* literals in the generic ConnectionService.updateCredentials was caught in review and refactored into a fully platform-neutral ConnectionCredentialsRewriterPort + registry — exactly mirroring the #586/#587 shape-validator pattern. Security-conscious, correctly layered, well-tested. Approve.
Architecture — the platform-leak fix is textbook ✅
ConnectionService.updateCredentialsnow dispatches throughConnectionCredentialsRewriterRegistryService.get(adapterKey)?.rewrite()and no-ops when no rewriter is registered — the onlyallegro/erlimentions in the file are explanatory comments; the code carries zero platform knowledge. This is precisely the CORE↔Integration boundary the docs demand ("no platform name leaks into core/host").- New surface is correctly placed: port in
domain/ports/, exception indomain/exceptions/(extendsError, sets.name,captureStackTrace— standards-compliant), registry ininfrastructure/adapters/, token inintegrations.tokens.ts, threaded throughHostServices+ plugin-sdk + all three host modules. TheErliCredentialsRewriterModulemirrors the existingErliWebhookProvisioningModuleshape (needs a Nest-injectedConnectionPortoutside the framework-neutralHostServicesbag — the right call). - All Allegro/Erli logic lives in
ErliAllegroCredentialsRewriterAdapter, and it's properly defensive: passthrough whenreuseAllegroConnectionIdis absent, and it throwsConnectionCredentialsRewriteExceptionon blank id / not-found / non-AllegroplatformType/ missing source client creds.
Security — verified end-to-end ✅
- Secret never hits a response.
@Roles('admin')@Put(':id/credentials')→HttpStatus.NO_CONTENT(empty body);updateCredentials(): Promise<void>resolves the pair, merges it into stored credentials (credentials.update(ref, { credentialsJson })), and returns nothing. The rawclientSecretis read server-side → persisted → never serialized. Matches the architecture-overview baseline ("never return secrets/credentials in API responses"). reuseAllegroConnectionIdis stripped by the rewriter and not persisted — only the resolved concreteallegroClientId/allegroClientSecretland in storage; the log line names the source connection but not the secret.- FE sends only
{ reuseAllegroConnectionId }on the reuse path (secret only on the manual path), and the panel test asserts the payload never contains the raw secret. Noanyanywhere in the diff.
🟢 One forward note (not a blocker) — the reuse surface is bounded only by admin + single-tenant
reuseAllegroConnectionId lets an admin copy any existing Allegro connection's secret into an Erli connection. The PR is honest about this: there's no tenant/org model, so @Roles('admin') + "must resolve to a real platformType:'allegro' connection" is the whole boundary. That's fine today — an admin already has full credential-management authority over every connection in a single-deployment instance, so this is not privilege escalation, and the existence + platform guard blocks garbage ids. The forward risk: when a tenant/organization model eventually lands, this rewriter needs an ownership check (source connection must belong to the caller's tenant), otherwise it becomes a cross-tenant credential-read vector. Worth a one-line TODO/ADR note in the adapter so it's not missed at that point — the JSDoc already flags the absence of tenancy, which is most of the way there.
Tests — strong ✅
Generic service delegation/passthrough/error-mapping (Allegro cases correctly moved out to the adapter spec); adapter spec's 7 cases cover success + every rejection branch (not-found, wrong-platform, missing creds, blank id, absent-passthrough); FE panel covers the zero-connections fallback, reuse radio + picker, the secret-free reuse payload, manual override, and reuse-without-selection validation.
Verdict
✅ Approve — clean platform-neutral refactor of a real boundary violation, server-side secret handling with the correct HTTP contract (admin-only, 204, void), and thorough tests. mergeable_state: clean onto the epic branch. This closes out the #1381 Erli-Allegro epic (plan → client → wiring → FE → credential reuse). Only follow-up is the tenant-ownership check whenever multi-tenancy arrives.
1b3f45c
into
epic/1381-erli-allegro-category-catalog
…tegory access (#1387) (#1405) Extends the #1384 Erli credentials panel with a reuse-vs-manual choice: when the operator already has an Allegro connection, its clientId/clientSecret can be copied server-side into the Erli connection instead of registering a second Allegro app. The raw clientSecret never round-trips through the browser. The credential-copy logic is dispatched through a new, platform-neutral ConnectionCredentialsRewriterPort + registry (mirroring the existing ConnectionConfigShapeValidatorPort/ConnectionCredentialsShapeValidatorPort pair) so the generic ConnectionService.updateCredentials stays unaware that Allegro or Erli exist. The Erli-specific resolution lives entirely in ErliAllegroCredentialsRewriterAdapter, registered from a companion ErliCredentialsRewriterModule (mirrors ErliWebhookProvisioningModule) since it needs ConnectionPort, which is intentionally outside the plugin-neutral HostServices bag. Closes #1387 Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…t a required Allegro connection (#1407) * feat(erli): Erli-owned Allegro client_credentials category-catalog client (#1388) * feat(erli): add Erli-owned Allegro client_credentials category-catalog client Adds AllegroCategoryCatalogClient, a self-contained HTTP client that lets an Erli connection browse Allegro's public /sale/categories and /sale/categories/{id}/parameters catalog via an Allegro app's grant_type=client_credentials token, with no dependency on @openlinker/integrations-allegro (per ADR-030) and no requirement for the operator to own a real Allegro seller connection. Extends ErliConnectionConfig with an optional allegroEnvironment field and ErliCredentials with optional allegroClientId/allegroClientSecret fields to carry the Allegro app credentials alongside Erli's own apiKey. Part of #1381, sub-task 1 of 3 (see #1383, #1384). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RpsVFTpKZ1HoowEUKzbnWu Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli): address PR #1388 review findings on AllegroCategoryCatalogClient - Extract inline wire-shape types to allegro-category-catalog-client.types.ts, matching the erli-http-client.ts / erli-http-client.types.ts split. - Add a single-flight guard around token acquisition so two concurrent fetchCategories/fetchCategoryParameters calls on a cold cache issue one grant_type=client_credentials request, not two. - Treat a token response missing expires_in as already-expired instead of caching it indefinitely. - Add tests for the 401/403 branch on a data call (categories/parameters), distinct from the token-endpoint rejection branch already covered. - Add a cross-plugin mapper parity test suite that runs Allegro's real sandbox category-parameters fixture through this client's copy of toNeutralCategoryParameter, mirroring assertions from allegro-category-parameter.mapper.spec.ts, with cross-referencing comments in both spec files so a future mapper change prompts updating both. - Replace the inline 'sandbox' | 'production' union with an as const + runtime-array AllegroCatalogEnvironment type in erli-connection.types.ts, matching AllegroConnectionConfig's own convention. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * feat(erli): wire per-connection Allegro category-catalog capability + credential/config validation (#1399) * feat(erli): wire per-connection Allegro category-catalog capability + credential/config validation (#1383) ErliOfferManagerAdapter now wires fetchCategories/fetchCategoryParameters as optional instance properties (never a static implements clause) so isCategoryBrowser/isCategoryParametersReader reflect whether a specific Erli connection has configured a valid Allegro app credential pair, per ADR-031. ErliAdapterFactory constructs the shared AllegroCategoryCatalogClient only when both allegroClientId and allegroClientSecret resolve to non-empty strings, resolving config.allegroEnvironment (default 'production'). The credentials shape validator now enforces "both or neither" for the Allegro credential pair, and the config shape validator restricts allegroEnvironment to 'sandbox' | 'production'. Verified CategoriesCacheService and ListingsController need no changes: both already resolve capabilities generically via getCapabilityAdapter + is* type guards, so a misconfigured Erli connection behaves like today's "adapter doesn't implement this capability" case. Added an integration test exercising the real ErliAdapterFactory + ErliOfferManagerAdapter + AllegroCategoryCatalogClient (fetch stubbed) through the production adapter-resolution seam and the real HTTP endpoint, covering configured / unconfigured / partially-configured connections. Confirmed the existing #1367 bulk-wizard capability-gate test passes unmodified. Part of #1381, sub-task 2 of 4 (depends on #1382, merged; see also #1384, #1387). Signed-off-by: Norbert Kulus <42zeroo@gmail.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli): add allegroCategoryAccessEnabled config flag as the FE-visible signal connection.supportedCapabilities turned out to be a static, per-adapterKey manifest value rather than computed per-connection-instance, so it can't distinguish an Erli connection with configured Allegro app credentials from one without. Add a non-secret ErliConnectionConfig.allegroCategoryAccessEnabled boolean for the frontend to read instead (see ADR-031 "Correction"); the write path that sets allegroClientId/allegroClientSecret must set/clear this flag atomically (tracked in #1384's scope). Also fixes stale ADR-030 references (renumbered to ADR-031 to avoid a collision with #1307) in allegro-category-catalog-client.ts and erli-connection.types.ts. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli): hoist Allegro category-token cache to host.cache + dedupe credential resolve (#1399 review) AllegroCategoryCatalogClient built a fresh in-memory token cache per adapter instance, but ErliAdapterFactory builds a fresh instance per getCapabilityAdapter call — so fetchCategoryParameters paid a full client_credentials OAuth round-trip on every request. The client now persists the acquired token in the optional CachePort (host.cache), keyed by clientId, with a TTL matching the token's remaining lifetime, so it survives across per-request instances and processes. Also collapses ErliAdapterFactory.createAdapters's two credentialsResolver.get calls (one for apiKey, one for the Allegro pair) into one shared resolve. Addresses both IMPORTANT findings from the PR #1399 review round (Piotr's token-cache note and the self-review's double-resolve finding). Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: Norbert Kulus <42zeroo@gmail.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * feat(erli,web): Allegro credentials checkbox + offer wizard category/parameters steps (#1401) * feat(erli,web): checkbox-reveal Allegro credentials panel + offer wizard category/parameters steps Adds the operator-facing half of the Erli-owned Allegro category-catalog feature (#1384, part of epic #1381, depends on #1382/#1383): - ErliCredentialsPanel gains a "Browse Allegro categories when creating Erli offers" checkbox that reveals Client ID / Client Secret fields (masked, show/hide toggle). Saving sequences two existing mutations from one click: useUpdateConnectionCredentialsMutation (credentials pair, merged with apiKey) then useUpdateConnectionMutation (config patch for allegroCategoryAccessEnabled) — credentials first so a failure never leaves the flag advertising access the backend can't serve. - ErliCreateOfferWizard renders the reused Allegro CategoryPicker + CategoryParametersStep (fed by the existing useCategoryParametersQuery) as dedicated Category / Category-parameters steps when connection.config.allegroCategoryAccessEnabled is true — per ADR-031's correction, this per-connection-instance config flag is the FE gating signal, not the static, per-adapterKey connection.supportedCapabilities. Falls back to today's plain-text field + a link to the connection's edit page otherwise. Stepper labels and needsProductParameters become capability-conditional; erliCreateOfferSchema gains a parameters slice serialized into overrides.parameters on submit (no BE change needed). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RpsVFTpKZ1HoowEUKzbnWu Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli,web): address PR #1401 review findings - Gate `canSubmit` in ErliCredentialsPanel on `allegroEnabled` so unchecking the Allegro-access box after typing (but not saving) Client ID/Secret disables Save instead of showing a false-positive "Credentials saved" toast while discarding the typed secret. - Drop the checkbox's invalid inline `accentColor: var(--accent)` (token doesn't exist) — `index.css` already applies `accent-color: var(--accent-primary)` globally. - Restore category-parameter values on Erli offer retry by reusing the Allegro retry mapper's `readParameters` heuristic (exported) instead of always prefilling an empty `parameters` object — both platforms persist the same neutral `overrides.parameters` shape. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli,connections): merge credential rotation + enforce category pick (#1401 second review) - ConnectionService.updateCredentials now merges the submitted payload onto the existing stored credentialsJson instead of replacing it wholesale, so rotating the plain Erli apiKey no longer silently wipes a previously configured allegroClientId/allegroClientSecret pair (and enabling Allegro access without touching apiKey no longer fails shape validation). - ErliCreateOfferWizard now blocks Next on the Category step until a category is actually selected, mirroring AllegroCreateOfferWizard's required categoryId field, instead of silently falling through to Category-parameters/Review with an empty category. - Carried over the approved mockup's helper copy under the Allegro Client ID / Client Secret fields and restored the checkbox's original hint text. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * test(erli,docs): cover mutation-sequencing failure paths + close ADR-031 documentation gaps (#1401 third review) - erli-credentials-panel.test.tsx: add regression tests asserting the config patch never fires when the credentials write rejects, and that a config-patch rejection after a successful credentials write leaves the flag/fields intact with a retryable inline error - the two claims the PR description makes about "the trickiest part of this issue" were previously unverified by the suite. - ADR-031: add a second Correction noting the sequencing is two FE-orchestrated mutations, not a single backend write, since no endpoint accepts both the credential pair and the config flag together; also record the wizard's 3-step-vs-5-step topology and the deferred credential-verification indicator as explicit, confirmed scope cuts rather than implicit deviations from the approved mockup. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * feat(erli): reuse an existing Allegro connection's credentials for category access (#1387) (#1405) Extends the #1384 Erli credentials panel with a reuse-vs-manual choice: when the operator already has an Allegro connection, its clientId/clientSecret can be copied server-side into the Erli connection instead of registering a second Allegro app. The raw clientSecret never round-trips through the browser. The credential-copy logic is dispatched through a new, platform-neutral ConnectionCredentialsRewriterPort + registry (mirroring the existing ConnectionConfigShapeValidatorPort/ConnectionCredentialsShapeValidatorPort pair) so the generic ConnectionService.updateCredentials stays unaware that Allegro or Erli exist. The Erli-specific resolution lives entirely in ErliAllegroCredentialsRewriterAdapter, registered from a companion ErliCredentialsRewriterModule (mirrors ErliWebhookProvisioningModule) since it needs ConnectionPort, which is intentionally outside the plugin-neutral HostServices bag. Closes #1387 Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Signed-off-by: Norbert Kulus <42zeroo@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…t a required Allegro connection (#1407) * feat(erli): Erli-owned Allegro client_credentials category-catalog client (#1388) * feat(erli): add Erli-owned Allegro client_credentials category-catalog client Adds AllegroCategoryCatalogClient, a self-contained HTTP client that lets an Erli connection browse Allegro's public /sale/categories and /sale/categories/{id}/parameters catalog via an Allegro app's grant_type=client_credentials token, with no dependency on @openlinker/integrations-allegro (per ADR-030) and no requirement for the operator to own a real Allegro seller connection. Extends ErliConnectionConfig with an optional allegroEnvironment field and ErliCredentials with optional allegroClientId/allegroClientSecret fields to carry the Allegro app credentials alongside Erli's own apiKey. Part of #1381, sub-task 1 of 3 (see #1383, #1384). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RpsVFTpKZ1HoowEUKzbnWu Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli): address PR #1388 review findings on AllegroCategoryCatalogClient - Extract inline wire-shape types to allegro-category-catalog-client.types.ts, matching the erli-http-client.ts / erli-http-client.types.ts split. - Add a single-flight guard around token acquisition so two concurrent fetchCategories/fetchCategoryParameters calls on a cold cache issue one grant_type=client_credentials request, not two. - Treat a token response missing expires_in as already-expired instead of caching it indefinitely. - Add tests for the 401/403 branch on a data call (categories/parameters), distinct from the token-endpoint rejection branch already covered. - Add a cross-plugin mapper parity test suite that runs Allegro's real sandbox category-parameters fixture through this client's copy of toNeutralCategoryParameter, mirroring assertions from allegro-category-parameter.mapper.spec.ts, with cross-referencing comments in both spec files so a future mapper change prompts updating both. - Replace the inline 'sandbox' | 'production' union with an as const + runtime-array AllegroCatalogEnvironment type in erli-connection.types.ts, matching AllegroConnectionConfig's own convention. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * feat(erli): wire per-connection Allegro category-catalog capability + credential/config validation (#1399) * feat(erli): wire per-connection Allegro category-catalog capability + credential/config validation (#1383) ErliOfferManagerAdapter now wires fetchCategories/fetchCategoryParameters as optional instance properties (never a static implements clause) so isCategoryBrowser/isCategoryParametersReader reflect whether a specific Erli connection has configured a valid Allegro app credential pair, per ADR-031. ErliAdapterFactory constructs the shared AllegroCategoryCatalogClient only when both allegroClientId and allegroClientSecret resolve to non-empty strings, resolving config.allegroEnvironment (default 'production'). The credentials shape validator now enforces "both or neither" for the Allegro credential pair, and the config shape validator restricts allegroEnvironment to 'sandbox' | 'production'. Verified CategoriesCacheService and ListingsController need no changes: both already resolve capabilities generically via getCapabilityAdapter + is* type guards, so a misconfigured Erli connection behaves like today's "adapter doesn't implement this capability" case. Added an integration test exercising the real ErliAdapterFactory + ErliOfferManagerAdapter + AllegroCategoryCatalogClient (fetch stubbed) through the production adapter-resolution seam and the real HTTP endpoint, covering configured / unconfigured / partially-configured connections. Confirmed the existing #1367 bulk-wizard capability-gate test passes unmodified. Part of #1381, sub-task 2 of 4 (depends on #1382, merged; see also #1384, #1387). Signed-off-by: Norbert Kulus <42zeroo@gmail.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli): add allegroCategoryAccessEnabled config flag as the FE-visible signal connection.supportedCapabilities turned out to be a static, per-adapterKey manifest value rather than computed per-connection-instance, so it can't distinguish an Erli connection with configured Allegro app credentials from one without. Add a non-secret ErliConnectionConfig.allegroCategoryAccessEnabled boolean for the frontend to read instead (see ADR-031 "Correction"); the write path that sets allegroClientId/allegroClientSecret must set/clear this flag atomically (tracked in #1384's scope). Also fixes stale ADR-030 references (renumbered to ADR-031 to avoid a collision with #1307) in allegro-category-catalog-client.ts and erli-connection.types.ts. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli): hoist Allegro category-token cache to host.cache + dedupe credential resolve (#1399 review) AllegroCategoryCatalogClient built a fresh in-memory token cache per adapter instance, but ErliAdapterFactory builds a fresh instance per getCapabilityAdapter call — so fetchCategoryParameters paid a full client_credentials OAuth round-trip on every request. The client now persists the acquired token in the optional CachePort (host.cache), keyed by clientId, with a TTL matching the token's remaining lifetime, so it survives across per-request instances and processes. Also collapses ErliAdapterFactory.createAdapters's two credentialsResolver.get calls (one for apiKey, one for the Allegro pair) into one shared resolve. Addresses both IMPORTANT findings from the PR #1399 review round (Piotr's token-cache note and the self-review's double-resolve finding). Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: Norbert Kulus <42zeroo@gmail.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * feat(erli,web): Allegro credentials checkbox + offer wizard category/parameters steps (#1401) * feat(erli,web): checkbox-reveal Allegro credentials panel + offer wizard category/parameters steps Adds the operator-facing half of the Erli-owned Allegro category-catalog feature (#1384, part of epic #1381, depends on #1382/#1383): - ErliCredentialsPanel gains a "Browse Allegro categories when creating Erli offers" checkbox that reveals Client ID / Client Secret fields (masked, show/hide toggle). Saving sequences two existing mutations from one click: useUpdateConnectionCredentialsMutation (credentials pair, merged with apiKey) then useUpdateConnectionMutation (config patch for allegroCategoryAccessEnabled) — credentials first so a failure never leaves the flag advertising access the backend can't serve. - ErliCreateOfferWizard renders the reused Allegro CategoryPicker + CategoryParametersStep (fed by the existing useCategoryParametersQuery) as dedicated Category / Category-parameters steps when connection.config.allegroCategoryAccessEnabled is true — per ADR-031's correction, this per-connection-instance config flag is the FE gating signal, not the static, per-adapterKey connection.supportedCapabilities. Falls back to today's plain-text field + a link to the connection's edit page otherwise. Stepper labels and needsProductParameters become capability-conditional; erliCreateOfferSchema gains a parameters slice serialized into overrides.parameters on submit (no BE change needed). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RpsVFTpKZ1HoowEUKzbnWu Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli,web): address PR #1401 review findings - Gate `canSubmit` in ErliCredentialsPanel on `allegroEnabled` so unchecking the Allegro-access box after typing (but not saving) Client ID/Secret disables Save instead of showing a false-positive "Credentials saved" toast while discarding the typed secret. - Drop the checkbox's invalid inline `accentColor: var(--accent)` (token doesn't exist) — `index.css` already applies `accent-color: var(--accent-primary)` globally. - Restore category-parameter values on Erli offer retry by reusing the Allegro retry mapper's `readParameters` heuristic (exported) instead of always prefilling an empty `parameters` object — both platforms persist the same neutral `overrides.parameters` shape. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli,connections): merge credential rotation + enforce category pick (#1401 second review) - ConnectionService.updateCredentials now merges the submitted payload onto the existing stored credentialsJson instead of replacing it wholesale, so rotating the plain Erli apiKey no longer silently wipes a previously configured allegroClientId/allegroClientSecret pair (and enabling Allegro access without touching apiKey no longer fails shape validation). - ErliCreateOfferWizard now blocks Next on the Category step until a category is actually selected, mirroring AllegroCreateOfferWizard's required categoryId field, instead of silently falling through to Category-parameters/Review with an empty category. - Carried over the approved mockup's helper copy under the Allegro Client ID / Client Secret fields and restored the checkbox's original hint text. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * test(erli,docs): cover mutation-sequencing failure paths + close ADR-031 documentation gaps (#1401 third review) - erli-credentials-panel.test.tsx: add regression tests asserting the config patch never fires when the credentials write rejects, and that a config-patch rejection after a successful credentials write leaves the flag/fields intact with a retryable inline error - the two claims the PR description makes about "the trickiest part of this issue" were previously unverified by the suite. - ADR-031: add a second Correction noting the sequencing is two FE-orchestrated mutations, not a single backend write, since no endpoint accepts both the credential pair and the config flag together; also record the wizard's 3-step-vs-5-step topology and the deferred credential-verification indicator as explicit, confirmed scope cuts rather than implicit deviations from the approved mockup. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * feat(erli): reuse an existing Allegro connection's credentials for category access (#1387) (#1405) Extends the #1384 Erli credentials panel with a reuse-vs-manual choice: when the operator already has an Allegro connection, its clientId/clientSecret can be copied server-side into the Erli connection instead of registering a second Allegro app. The raw clientSecret never round-trips through the browser. The credential-copy logic is dispatched through a new, platform-neutral ConnectionCredentialsRewriterPort + registry (mirroring the existing ConnectionConfigShapeValidatorPort/ConnectionCredentialsShapeValidatorPort pair) so the generic ConnectionService.updateCredentials stays unaware that Allegro or Erli exist. The Erli-specific resolution lives entirely in ErliAllegroCredentialsRewriterAdapter, registered from a companion ErliCredentialsRewriterModule (mirrors ErliWebhookProvisioningModule) since it needs ConnectionPort, which is intentionally outside the plugin-neutral HostServices bag. Closes #1387 Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Signed-off-by: Norbert Kulus <42zeroo@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Closes #1387. Part of epic #1381 (final sub-task).
Summary
Extends the #1384 Erli credentials panel (PR #1401) with a reuse-vs-manual choice for the Allegro app credentials used for category-catalog access: when the operator already has an Allegro connection configured, its
clientId/clientSecretcan be reused instead of registering a second Allegro app. Matches the approved mockup (both states).Security: the raw Allegro
clientSecretis resolved and copied entirely server-side — it is never serialized into any HTTP response body, and the frontend never sees it.Architecture note
The initial implementation embedded the reuse-resolution logic (with
'allegro'/allegroClientId/allegroClientSecretliterals) directly inside the generic, cross-platformConnectionService.updateCredentials. That was caught in review as a "platform name leaks into core/host layer" violation and refactored before this PR was opened.The shipped design instead introduces a new, fully platform-neutral
ConnectionCredentialsRewriterPort+ConnectionCredentialsRewriterRegistryServiceinlibs/core/src/integrations/, mirroring the existingConnectionConfigShapeValidatorPort/ConnectionCredentialsShapeValidatorPortpair.ConnectionService.updateCredentialsnow just looks up a rewriter byadapterKeyand no-ops when none is registered — it has zero knowledge of Allegro or Erli.All Erli/Allegro-specific logic (resolve
reuseAllegroConnectionId→ read the source connection's credentials → validate it's an Allegro connection with client credentials configured) lives inErliAllegroCredentialsRewriterAdapter, registered from a new companionErliCredentialsRewriterModule(mirrors the existingErliWebhookProvisioningModuleshape) since the adapter needs a NestJS-injectedConnectionPortthat's intentionally outside the framework-neutralHostServicesbag.Backend
libs/core/src/integrations/domain/ports/connection-credentials-rewriter.port.ts— new portlibs/core/src/integrations/domain/exceptions/connection-credentials-rewrite.exception.ts— new domain exceptionlibs/core/src/integrations/infrastructure/adapters/connection-credentials-rewriter-registry.service.ts— new registryapps/api/.../connection.service.ts—updateCredentialsnow dispatches through the registry (generic, no platform knowledge)libs/integrations/erli/src/infrastructure/adapters/erli-allegro-credentials-rewriter.adapter.ts— the actual reuse logiclibs/integrations/erli/src/erli-credentials-rewriter.module.ts— registers the adapterHostServices.connectionCredentialsRewriterRegistrythreaded throughlibs/plugin-sdkand all three host-plugin modules (Allegro, PrestaShop, Erli) that build the bag manuallyTenant/ownership note: this codebase has no tenant/organization model — there's no
tenantId/organizationIdanywhere onConnection.@Roles('admin')on the credentials endpoint is the only access boundary that exists today, applied uniformly across the single-deployment instance. The closest enforceable check is that the reuse-source id must resolve to a real, existingplatformType: 'allegro'connection — anything else is rejected. Documented inline in the adapter's JSDoc.Frontend
ErliCredentialsPanel(apps/web/src/plugins/erli/components/erli-credentials-panel.tsx): when ≥1 active Allegro connection exists, shows a "Reuse credentials from an existing Allegro connection" / "Enter Allegro app credentials manually" radio choice with a connection picker, per the approved mockup. Zero Allegro connections falls back to manual-only entry (today's #1384 behavior) with a notice. The reuse path sends{ reuseAllegroConnectionId }and follows the same sequenced, fail-safe-ordered write pattern established in #1401 (credentials first, then theallegroCategoryAccessEnabledconfig patch).Tests
connection.service.spec.ts— generic passthrough/delegation/error-mapping tests against a stub rewriter (no Allegro-specific cases; those moved out)erli-allegro-credentials-rewriter.adapter.spec.ts— new, 7 cases (successful reuse, not-found, wrong platform, missing client credentials, blank id, passthrough when absent)erli-credentials-panel.test.tsx— zero-connections notice, reuse radio + picker, successful reuse submit (asserts payload never contains the raw secret), manual override still works, reuse-without-selection validationQuality gate
Full-repo
pnpm lint,pnpm check:invariants,pnpm type-check, andpnpm testall green (confirmed independently in an isolated worktree before this PR was opened).