Skip to content

Commit aea932e

Browse files
committed
fix(foxy-store-form): adjust store secret validations and add read-only values back to ui
1 parent 1c3f0cf commit aea932e

File tree

5 files changed

+181
-76
lines changed

5 files changed

+181
-76
lines changed

src/elements/public/StoreForm/StoreForm.test.ts

+68-53
Original file line numberDiff line numberDiff line change
@@ -392,107 +392,122 @@ describe('StoreForm', () => {
392392
expect(form.errors).to.include('webhook-url:v8n_too_long');
393393
});
394394

395-
it('produces the webhook-key:v8n_required error if legacy webhook is enabled and key is empty', () => {
395+
it('produces the webhook-key:v8n_required error if key is empty', () => {
396396
const form = new Form();
397397
expect(form.errors).to.not.include('webhook-key:v8n_required');
398398

399-
form.edit({ use_webhook: true, webhook_key: '' });
399+
form.edit({ webhook_key: '' });
400400
expect(form.errors).to.include('webhook-key:v8n_required');
401-
});
402401

403-
it('produces the webhook-key:v8n_required error if hmac for carts is enabled and key is empty', () => {
404-
const form = new Form();
402+
form.edit({ webhook_key: 'abc' });
405403
expect(form.errors).to.not.include('webhook-key:v8n_required');
406-
407-
form.edit({ use_cart_validation: true, webhook_key: '' });
408-
expect(form.errors).to.include('webhook-key:v8n_required');
409404
});
410405

411-
it('produces the webhook-key:v8n_too_long error if legacy webhook is enabled and the key exceeds 500 characters', () => {
406+
it('produces the webhook-key:v8n_too_long error if legacy webhook is enabled and the key exceeds 100 characters', () => {
412407
const form = new Form();
413408

414409
form.edit({ use_webhook: false, webhook_key: '' });
415410
expect(form.errors).to.not.include('webhook-key:v8n_too_long');
416411

417-
form.edit({ use_webhook: false, webhook_key: 'A'.repeat(500) });
412+
form.edit({ use_webhook: false, webhook_key: 'A'.repeat(100) });
418413
expect(form.errors).to.not.include('webhook-key:v8n_too_long');
419414

420-
form.edit({ use_webhook: false, webhook_key: 'A'.repeat(501) });
415+
form.edit({ use_webhook: false, webhook_key: 'A'.repeat(101) });
421416
expect(form.errors).to.include('webhook-key:v8n_too_long');
422417

423418
form.edit({ use_webhook: true, webhook_key: '' });
424419
expect(form.errors).to.not.include('webhook-key:v8n_too_long');
425420

426-
form.edit({ use_webhook: true, webhook_key: 'A'.repeat(500) });
421+
form.edit({ use_webhook: true, webhook_key: 'A'.repeat(100) });
427422
expect(form.errors).to.not.include('webhook-key:v8n_too_long');
428423

429424
form.edit({ use_webhook: true, webhook_key: 'A'.repeat(501) });
430425
expect(form.errors).to.include('webhook-key:v8n_too_long');
431426
});
432427

433-
[
434-
'use-webhook:v8n_webhook_key_required',
435-
'webhook-key-xml-datafeed:v8n_required',
436-
'webhook-key:v8n_required',
437-
].forEach(code => {
438-
it(`produces the ${code} error when XML datafeed is enabled but webhook key is empty`, () => {
439-
const form = new Form();
440-
expect(form.errors).to.not.include(code);
428+
it(`produces the use-webhook:v8n_webhook_key_required error when XML datafeed is enabled but webhook key is empty`, () => {
429+
const form = new Form();
430+
expect(form.errors).to.not.include('use-webhook:v8n_webhook_key_required');
441431

442-
form.edit({ use_webhook: true, webhook_key: '' });
443-
expect(form.errors).to.include(code);
432+
form.edit({ use_webhook: true, webhook_key: '' });
433+
expect(form.errors).to.include('use-webhook:v8n_webhook_key_required');
444434

445-
form.edit({ webhook_key: 'abc' });
446-
expect(form.errors).to.not.include(code);
435+
form.edit({ webhook_key: 'abc' });
436+
expect(form.errors).to.not.include('use-webhook:v8n_webhook_key_required');
447437

448-
const props = { cart_signing: '', xml_datafeed: '', api_legacy: '', sso: '' };
449-
form.edit({ webhook_key: JSON.stringify(props) });
450-
expect(form.errors).to.include(code);
438+
const props = { cart_signing: '', xml_datafeed: '', api_legacy: '', sso: '' };
439+
form.edit({ webhook_key: JSON.stringify(props) });
440+
expect(form.errors).to.include('use-webhook:v8n_webhook_key_required');
451441

452-
form.edit({ webhook_key: JSON.stringify({ ...props, xml_datafeed: 'abc' }) });
453-
expect(form.errors).to.not.include(code);
454-
});
442+
form.edit({ webhook_key: JSON.stringify({ ...props, xml_datafeed: 'abc' }) });
443+
expect(form.errors).to.not.include('use-webhook:v8n_webhook_key_required');
455444
});
456445

457-
[
458-
'use-cart-validation:v8n_webhook_key_required',
459-
'webhook-key-cart-signing:v8n_required',
460-
'webhook-key:v8n_required',
461-
].forEach(code => {
462-
it(`produces the ${code} error when cart signing is enabled but webhook key is empty`, () => {
463-
const form = new Form();
464-
expect(form.errors).to.not.include(code);
446+
it(`produces the use-cart-validation:v8n_webhook_key_required error when XML datafeed is enabled but webhook key is empty`, () => {
447+
const form = new Form();
448+
expect(form.errors).to.not.include('use-cart-validation:v8n_webhook_key_required');
449+
450+
form.edit({ use_cart_validation: true, webhook_key: '' });
451+
expect(form.errors).to.include('use-cart-validation:v8n_webhook_key_required');
465452

466-
form.edit({ use_cart_validation: true, webhook_key: '' });
467-
expect(form.errors).to.include(code);
453+
form.edit({ webhook_key: 'abc' });
454+
expect(form.errors).to.not.include('use-cart-validation:v8n_webhook_key_required');
468455

469-
form.edit({ webhook_key: 'abc' });
470-
expect(form.errors).to.not.include(code);
456+
const props = { cart_signing: '', xml_datafeed: '', api_legacy: '', sso: '' };
457+
form.edit({ webhook_key: JSON.stringify(props) });
458+
expect(form.errors).to.include('use-cart-validation:v8n_webhook_key_required');
459+
460+
form.edit({ webhook_key: JSON.stringify({ ...props, cart_signing: 'abc' }) });
461+
expect(form.errors).to.not.include('use-cart-validation:v8n_webhook_key_required');
462+
});
463+
464+
it(`produces the use-single-sign-on:v8n_webhook_key_required error when XML datafeed is enabled but webhook key is empty`, () => {
465+
const form = new Form();
466+
expect(form.errors).to.not.include('use-single-sign-on:v8n_webhook_key_required');
467+
468+
form.edit({ use_single_sign_on: true, webhook_key: '' });
469+
expect(form.errors).to.include('use-single-sign-on:v8n_webhook_key_required');
470+
471+
form.edit({ webhook_key: 'abc' });
472+
expect(form.errors).to.not.include('use-single-sign-on:v8n_webhook_key_required');
473+
474+
const props = { cart_signing: '', xml_datafeed: '', api_legacy: '', sso: '' };
475+
form.edit({ webhook_key: JSON.stringify(props) });
476+
expect(form.errors).to.include('use-single-sign-on:v8n_webhook_key_required');
477+
478+
form.edit({ webhook_key: JSON.stringify({ ...props, sso: 'abc' }) });
479+
expect(form.errors).to.not.include('use-single-sign-on:v8n_webhook_key_required');
480+
});
481+
482+
(['xml_datafeed', 'cart_signing', 'api_legacy', 'sso'] as const).forEach(prop => {
483+
const requiredCode = `webhook-key-${prop.replace(/_/g, '-')}:v8n_required`;
484+
const tooLongCode = `webhook-key-${prop.replace(/_/g, '-')}:v8n_too_long`;
485+
486+
it(`produces the ${requiredCode} error when ${prop} in webhook_key JSON is empty`, () => {
487+
const form = new Form();
488+
expect(form.errors).to.not.include(requiredCode);
471489

472490
const props = { cart_signing: '', xml_datafeed: '', api_legacy: '', sso: '' };
473491
form.edit({ webhook_key: JSON.stringify(props) });
474-
expect(form.errors).to.include(code);
492+
expect(form.errors).to.include(requiredCode);
475493

476-
form.edit({ webhook_key: JSON.stringify({ ...props, cart_signing: 'abc' }) });
477-
expect(form.errors).to.not.include(code);
494+
form.edit({ webhook_key: JSON.stringify({ ...props, [prop]: 'abc' }) });
495+
expect(form.errors).to.not.include(requiredCode);
478496
});
479-
});
480497

481-
(['xml_datafeed', 'cart_signing', 'api_legacy', 'sso'] as const).forEach(prop => {
482-
const code = `webhook-key-${prop.replace(/_/g, '-')}:v8n_too_long`;
483-
it(`produces the ${code} error when ${prop} in webhook_key JSON is more than 100 characters long`, () => {
498+
it(`produces the ${tooLongCode} error when ${prop} in webhook_key JSON is more than 100 characters long`, () => {
484499
const form = new Form();
485-
expect(form.errors).to.not.include(code);
500+
expect(form.errors).to.not.include(tooLongCode);
486501

487502
const props = { cart_signing: '', xml_datafeed: '', api_legacy: '', sso: '' };
488503
form.edit({ webhook_key: JSON.stringify(props) });
489-
expect(form.errors).to.not.include(code);
504+
expect(form.errors).to.not.include(tooLongCode);
490505

491506
form.edit({ webhook_key: JSON.stringify({ ...props, [prop]: 'a'.repeat(100) }) });
492-
expect(form.errors).to.not.include(code);
507+
expect(form.errors).to.not.include(tooLongCode);
493508

494509
form.edit({ webhook_key: JSON.stringify({ ...props, [prop]: 'a'.repeat(101) }) });
495-
expect(form.errors).to.include(code);
510+
expect(form.errors).to.include(tooLongCode);
496511
});
497512
});
498513

src/elements/public/StoreForm/StoreForm.ts

+71-18
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export class StoreForm extends Base<Data> {
4747
reportingStoreDomainExists: { attribute: 'reporting-store-domain-exists' },
4848
customerPasswordHashTypes: { attribute: 'customer-password-hash-types' },
4949
shippingAddressTypes: { attribute: 'shipping-address-types' },
50+
storeSecretsPageUrl: { attribute: 'store-secrets-page-url' },
5051
hCaptchaSiteKey: { attribute: 'h-captcha-site-key' },
5152
storeVersions: { attribute: 'store-versions' },
5253
checkoutTypes: { attribute: 'checkout-types' },
@@ -110,35 +111,41 @@ export class StoreForm extends Base<Data> {
110111
({ webhook_key: v }, host) => {
111112
// TODO remove the line below when API limit is corrected to match the legacy admin
112113
if (host.data?.webhook_key === v) return true;
113-
return !v || v.length <= 500 || 'webhook-key:v8n_too_long';
114+
return !v || v.length <= 100 || 'webhook-key:v8n_too_long';
114115
},
115116

116-
...[
117-
'use-webhook:v8n_webhook_key_required',
118-
'webhook-key-xml-datafeed:v8n_required',
119-
'webhook-key:v8n_required',
120-
].map(code => ({ webhook_key: v, use_webhook: on }: Partial<Data>) => {
117+
({ webhook_key: v, use_webhook: on }: Partial<Data>) => {
121118
const parsedV = parseWebhookKey(v ?? '');
119+
const code = 'use-webhook:v8n_webhook_key_required';
122120
return !on || !!(parsedV ? parsedV.xml_datafeed : v) || code;
123-
}),
121+
},
124122

125-
...[
126-
'use-cart-validation:v8n_webhook_key_required',
127-
'webhook-key-cart-signing:v8n_required',
128-
'webhook-key:v8n_required',
129-
].map(code => ({ webhook_key: v, use_cart_validation: on }: Partial<Data>) => {
123+
({ webhook_key: v, use_cart_validation: on }: Partial<Data>) => {
130124
const parsedV = parseWebhookKey(v ?? '');
125+
const code = 'use-cart-validation:v8n_webhook_key_required';
131126
return !on || !!(parsedV ? parsedV.cart_signing : v) || code;
132-
}),
127+
},
128+
129+
({ webhook_key: v, use_single_sign_on: on }: Partial<Data>) => {
130+
const parsedV = parseWebhookKey(v ?? '');
131+
const code = 'use-single-sign-on:v8n_webhook_key_required';
132+
return !on || !!(parsedV ? parsedV.sso : v) || code;
133+
},
133134

134-
...(['xml_datafeed', 'cart_signing', 'api_legacy', 'sso'] as const).map(
135-
prop =>
135+
...(['xml_datafeed', 'cart_signing', 'api_legacy', 'sso'] as const)
136+
.map(prop => [
137+
({ webhook_key: v }: Partial<Data>) => {
138+
const parsedV = parseWebhookKey(v ?? '');
139+
const code = `webhook-key-${prop.replace(/_/g, '-')}:v8n_required`;
140+
return !parsedV || !!parsedV[prop] || code;
141+
},
136142
({ webhook_key: v }: Partial<Data>) => {
137143
const parsedV = parseWebhookKey(v ?? '');
138144
const code = `webhook-key-${prop.replace(/_/g, '-')}:v8n_too_long`;
139145
return !parsedV || parsedV[prop].length <= 100 || code;
140-
}
141-
),
146+
},
147+
])
148+
.flat(),
142149

143150
({ single_sign_on_url: v, use_single_sign_on: on }) => {
144151
return !on || !!v || 'single-sign-on-url:v8n_required';
@@ -167,6 +174,9 @@ export class StoreForm extends Base<Data> {
167174
/** URL of the `fx:shipping_address_types` property helper resource. */
168175
shippingAddressTypes: string | null = null;
169176

177+
/** URL of the Store Secrets settings page if you are using this form on multiple pages. */
178+
storeSecretsPageUrl: string | null = null;
179+
170180
/** hCaptcha site key for signup verification. If provided, requires users to complete a captcha before creating a store. */
171181
hCaptchaSiteKey: string | null = null;
172182

@@ -479,6 +489,12 @@ export class StoreForm extends Base<Data> {
479489
const displayIdExamples = this.__displayIdExamples;
480490
const journalIdExamples = this.__journalIdExamples;
481491

492+
const rawWebhookKey = this.data?.webhook_key ?? '';
493+
const parsedWebhookKey = parseWebhookKey(rawWebhookKey);
494+
const cartSigningKey = parsedWebhookKey?.cart_signing ?? rawWebhookKey;
495+
const xmlDatafeedKey = parsedWebhookKey?.xml_datafeed ?? rawWebhookKey;
496+
const ssoKey = parsedWebhookKey?.sso ?? rawWebhookKey;
497+
482498
return html`
483499
${this.renderHeader()}
484500
@@ -565,7 +581,7 @@ export class StoreForm extends Base<Data> {
565581
</foxy-internal-select-control>
566582
</foxy-internal-summary-control>
567583
568-
<foxy-internal-summary-control infer="store-secrets">
584+
<foxy-internal-summary-control infer="store-secrets" id="store-secrets">
569585
<foxy-internal-switch-control
570586
infer="use-single-secret"
571587
.getValue=${this.__useSingleWebhookKeyGetValue}
@@ -727,6 +743,10 @@ export class StoreForm extends Base<Data> {
727743
728744
<foxy-internal-switch-control infer="use-cart-validation" helper-text-as-tooltip>
729745
</foxy-internal-switch-control>
746+
747+
${cartSigningKey && this.form.use_cart_validation
748+
? this.__renderReadonlyWebhookKey('webhook-key-cart-signing', cartSigningKey)
749+
: ''}
730750
</foxy-internal-summary-control>
731751
732752
<foxy-internal-summary-control infer="checkout">
@@ -776,6 +796,7 @@ export class StoreForm extends Base<Data> {
776796
? html`
777797
<foxy-internal-text-control layout="summary-item" infer="single-sign-on-url">
778798
</foxy-internal-text-control>
799+
${ssoKey ? this.__renderReadonlyWebhookKey('webhook-key-sso', ssoKey) : ''}
779800
`
780801
: ''}
781802
</foxy-internal-summary-control>
@@ -964,6 +985,9 @@ export class StoreForm extends Base<Data> {
964985
? html`
965986
<foxy-internal-text-control layout="summary-item" infer="webhook-url">
966987
</foxy-internal-text-control>
988+
${xmlDatafeedKey
989+
? this.__renderReadonlyWebhookKey('webhook-key-xml-datafeed', xmlDatafeedKey)
990+
: ''}
967991
`
968992
: ''}
969993
</foxy-internal-summary-control>
@@ -1205,4 +1229,33 @@ export class StoreForm extends Base<Data> {
12051229
`,
12061230
};
12071231
}
1232+
1233+
private __renderReadonlyWebhookKey(scope: string, key: string) {
1234+
return html`
1235+
<div class="leading-xs">
1236+
<div class="flex items-center gap-s">
1237+
<p class="flex-1">
1238+
<foxy-i18n infer=${scope} key="label"></foxy-i18n>
1239+
</p>
1240+
<p class="text-tertiary">${'*'.repeat(8)}${key.substr(-4)}</p>
1241+
<foxy-copy-to-clipboard
1242+
layout="complete"
1243+
infer="${scope} copy-to-clipboard"
1244+
theme="contrast tertiary-inline"
1245+
text=${key}
1246+
>
1247+
</foxy-copy-to-clipboard>
1248+
</div>
1249+
<p class="text-xs text-secondary">
1250+
<foxy-i18n infer=${scope} key="helper_text"></foxy-i18n>
1251+
<a
1252+
class="text-body font-medium rounded-s hover-underline focus-outline-none focus-ring-2 focus-ring-primary-50"
1253+
href=${this.storeSecretsPageUrl ?? '#store-secrets'}
1254+
>
1255+
<foxy-i18n infer=${scope} key="link_text"></foxy-i18n>
1256+
</a>
1257+
</p>
1258+
</div>
1259+
`;
1260+
}
12081261
}

src/elements/public/StoreForm/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import '../../internal/InternalSelectControl/index';
1010
import '../../internal/InternalTextControl/index';
1111
import '../../internal/InternalForm/index';
1212

13+
import '../CopyToClipboard/index';
1314
import '../NucleonElement/index';
1415
import '../I18n/index';
1516

src/server/hapi/createDataset.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ export const createDataset: () => Dataset = () => ({
618618
checkout_type: 'default_account',
619619
use_webhook: false,
620620
webhook_url: '',
621-
webhook_key: '',
621+
webhook_key: 'A'.repeat(32),
622622
use_cart_validation: false,
623623
use_single_sign_on: false,
624624
single_sign_on_url: '',

0 commit comments

Comments
 (0)