@descope-ui/descope-ponyhot 2.2.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/e2e/descope-honeypot.spec.ts +174 -0
- package/package.json +32 -0
- package/project.json +8 -0
- package/src/component/HoneypotClass.js +106 -0
- package/src/component/index.js +5 -0
- package/stories/descope-honeypot.stories.js +40 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
|
+
|
|
5
|
+
## [2.2.37](https://github.com/descope/web-components-ui/compare/web-components-ui-2.2.36...web-components-ui-2.2.37) (2026-01-28)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* Rename honeypot folder to resolve registration mismatch ([#868](https://github.com/descope/web-components-ui/issues/868)) ([7166c17](https://github.com/descope/web-components-ui/commit/7166c1799fa79bf1b1504eb93ca48fd8a486bf53))
|
|
11
|
+
|
|
12
|
+
## [2.2.36](https://github.com/descope/web-components-ui/compare/web-components-ui-2.2.35...web-components-ui-2.2.36) (2026-01-28)
|
|
13
|
+
|
|
14
|
+
## [2.2.35](https://github.com/descope/web-components-ui/compare/web-components-ui-2.2.34...web-components-ui-2.2.35) (2026-01-22)
|
|
15
|
+
|
|
16
|
+
## [2.2.34](https://github.com/descope/web-components-ui/compare/web-components-ui-2.2.33...web-components-ui-2.2.34) (2026-01-21)
|
|
17
|
+
|
|
18
|
+
## [2.2.33](https://github.com/descope/web-components-ui/compare/web-components-ui-2.2.32...web-components-ui-2.2.33) (2026-01-21)
|
|
19
|
+
|
|
20
|
+
## [2.2.32](https://github.com/descope/web-components-ui/compare/web-components-ui-2.2.31...web-components-ui-2.2.32) (2026-01-21)
|
|
21
|
+
|
|
22
|
+
## [2.2.31](https://github.com/descope/web-components-ui/compare/web-components-ui-2.2.30...web-components-ui-2.2.31) (2026-01-21)
|
|
23
|
+
|
|
24
|
+
## [2.2.30](https://github.com/descope/web-components-ui/compare/web-components-ui-2.2.29...web-components-ui-2.2.30) (2026-01-19)
|
|
25
|
+
|
|
26
|
+
## [2.2.29](https://github.com/descope/web-components-ui/compare/web-components-ui-2.2.28...web-components-ui-2.2.29) (2026-01-19)
|
|
27
|
+
|
|
28
|
+
## [2.2.28](https://github.com/descope/web-components-ui/compare/web-components-ui-2.2.27...web-components-ui-2.2.28) (2026-01-19)
|
|
29
|
+
|
|
30
|
+
## [2.2.27](https://github.com/descope/web-components-ui/compare/web-components-ui-2.2.26...web-components-ui-2.2.27) (2026-01-18)
|
|
31
|
+
|
|
32
|
+
# Changelog
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
import { getStoryUrl } from 'e2e-utils';
|
|
3
|
+
import { createHoneypotTestDriver } from 'test-drivers';
|
|
4
|
+
|
|
5
|
+
const storyName = 'descope-honeypot';
|
|
6
|
+
// NOTICE: This component registers with a DIFFERENT name than its file name
|
|
7
|
+
const componentName = 'descope-ponyhot';
|
|
8
|
+
|
|
9
|
+
test.describe('logic', () => {
|
|
10
|
+
test('render honeypot input', async ({ page }) => {
|
|
11
|
+
await page.goto(
|
|
12
|
+
getStoryUrl(storyName, {
|
|
13
|
+
label: 'Enter address',
|
|
14
|
+
type: 'address',
|
|
15
|
+
name: 'dhp-address',
|
|
16
|
+
}),
|
|
17
|
+
{
|
|
18
|
+
waitUntil: 'networkidle',
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const component = createHoneypotTestDriver(page.locator(componentName));
|
|
23
|
+
|
|
24
|
+
expect(await component.input.getAttribute('name')).toMatch('dhp-address');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
[
|
|
28
|
+
'input',
|
|
29
|
+
'change',
|
|
30
|
+
'click',
|
|
31
|
+
'keydown',
|
|
32
|
+
'keyup',
|
|
33
|
+
'mouseover',
|
|
34
|
+
'mousedown',
|
|
35
|
+
].forEach((eventType) => {
|
|
36
|
+
['text', 'email', 'address', 'password'].forEach((inputType) => {
|
|
37
|
+
test(`fill honeypot of type "${inputType}" when event "${eventType}" dispatched`, async ({
|
|
38
|
+
page,
|
|
39
|
+
}) => {
|
|
40
|
+
await page.goto(
|
|
41
|
+
getStoryUrl(storyName, {
|
|
42
|
+
label: 'Enter value',
|
|
43
|
+
type: inputType,
|
|
44
|
+
name: `dhp-${inputType}`,
|
|
45
|
+
}),
|
|
46
|
+
{
|
|
47
|
+
waitUntil: 'networkidle',
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const component = createHoneypotTestDriver(page.locator(componentName));
|
|
52
|
+
await component.input.dispatchEvent(eventType);
|
|
53
|
+
|
|
54
|
+
expect(await component.getInputValue()).toBe(`dhp-${eventType}`);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should apply hiding styles to component', async ({ page }) => {
|
|
60
|
+
await page.goto(
|
|
61
|
+
getStoryUrl(storyName, {
|
|
62
|
+
label: 'Enter address',
|
|
63
|
+
type: 'address',
|
|
64
|
+
name: 'dhp-address',
|
|
65
|
+
}),
|
|
66
|
+
{
|
|
67
|
+
waitUntil: 'networkidle',
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const component = createHoneypotTestDriver(page.locator(componentName));
|
|
72
|
+
|
|
73
|
+
const expectedStyles = {
|
|
74
|
+
position: 'absolute',
|
|
75
|
+
opacity: '0.01',
|
|
76
|
+
width: '1px',
|
|
77
|
+
height: '1px',
|
|
78
|
+
'pointer-events': 'none',
|
|
79
|
+
'z-index': '-1',
|
|
80
|
+
left: '-999999px',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const computedStyle = await component.getHideStyles();
|
|
84
|
+
|
|
85
|
+
Object.entries(expectedStyles).forEach(([styleName, expectedValue]) => {
|
|
86
|
+
expect(computedStyle[styleName]).toBe(expectedValue);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(await component.getInputInlineStyle()).toBeNull();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('should only trigger once, then lock state', async ({ page }) => {
|
|
93
|
+
await page.goto(
|
|
94
|
+
getStoryUrl(storyName, {
|
|
95
|
+
label: 'Enter address',
|
|
96
|
+
type: 'address',
|
|
97
|
+
name: 'dhp-address',
|
|
98
|
+
}),
|
|
99
|
+
{
|
|
100
|
+
waitUntil: 'networkidle',
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const component = createHoneypotTestDriver(page.locator(componentName));
|
|
105
|
+
|
|
106
|
+
component.input.dispatchEvent('input');
|
|
107
|
+
expect(await component.getInputValue()).toBe('dhp-input');
|
|
108
|
+
|
|
109
|
+
component.input.dispatchEvent('click');
|
|
110
|
+
expect(await component.getInputValue()).toBe('dhp-input');
|
|
111
|
+
|
|
112
|
+
component.input.dispatchEvent('keyup');
|
|
113
|
+
expect(await component.getInputValue()).toBe('dhp-input');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('should intercept direct value assignment without events', async ({
|
|
117
|
+
page,
|
|
118
|
+
}) => {
|
|
119
|
+
await page.goto(
|
|
120
|
+
getStoryUrl(storyName, {
|
|
121
|
+
label: 'Enter address',
|
|
122
|
+
type: 'address',
|
|
123
|
+
name: 'dhp-address',
|
|
124
|
+
}),
|
|
125
|
+
{
|
|
126
|
+
waitUntil: 'networkidle',
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const component = createHoneypotTestDriver(page.locator(componentName));
|
|
131
|
+
await component.setInputValue('bot-spam-value');
|
|
132
|
+
|
|
133
|
+
expect(await component.getInputValue()).toBe('dhp-direct-set');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('should allow setting dhp- prefixed values through interceptor', async ({
|
|
137
|
+
page,
|
|
138
|
+
}) => {
|
|
139
|
+
await page.goto(
|
|
140
|
+
getStoryUrl(storyName, {
|
|
141
|
+
label: 'Enter address',
|
|
142
|
+
type: 'address',
|
|
143
|
+
name: 'dhp-address',
|
|
144
|
+
}),
|
|
145
|
+
{
|
|
146
|
+
waitUntil: 'networkidle',
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const component = createHoneypotTestDriver(page.locator(componentName));
|
|
151
|
+
await component.setInputValue('dhp-click');
|
|
152
|
+
|
|
153
|
+
expect(await component.getInputValue()).toBe('dhp-click');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('should not intercept empty value assignments', async ({ page }) => {
|
|
157
|
+
await page.goto(
|
|
158
|
+
getStoryUrl(storyName, {
|
|
159
|
+
label: 'Enter address',
|
|
160
|
+
type: 'address',
|
|
161
|
+
name: 'dhp-address',
|
|
162
|
+
}),
|
|
163
|
+
{
|
|
164
|
+
waitUntil: 'networkidle',
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const component = createHoneypotTestDriver(page.locator(componentName));
|
|
169
|
+
|
|
170
|
+
await component.setInputValue('');
|
|
171
|
+
|
|
172
|
+
expect(await component.getInputValue()).toBe('');
|
|
173
|
+
});
|
|
174
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@descope-ui/descope-ponyhot",
|
|
3
|
+
"version": "2.2.37",
|
|
4
|
+
"exports": {
|
|
5
|
+
".": {
|
|
6
|
+
"import": "./src/component/index.js"
|
|
7
|
+
},
|
|
8
|
+
"./theme": {
|
|
9
|
+
"import": "./src/theme.js"
|
|
10
|
+
},
|
|
11
|
+
"./class": {
|
|
12
|
+
"import": "./src/component/HoneypotClass.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@playwright/test": "1.38.1",
|
|
17
|
+
"e2e-utils": "2.2.37",
|
|
18
|
+
"test-assets": "2.2.37",
|
|
19
|
+
"test-drivers": "2.2.37"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@descope-ui/common": "2.2.37",
|
|
23
|
+
"@descope-ui/theme-globals": "2.2.37"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"link-workspace-packages": false
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"test": "echo 'No tests defined' && exit 0",
|
|
30
|
+
"test:e2e": "echo 'No e2e tests defined' && exit 0"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { componentNameValidationMixin } from '@descope-ui/common/components-mixins';
|
|
2
|
+
import { createBaseClass } from '@descope-ui/common/base-classes';
|
|
3
|
+
import { getComponentName } from '@descope-ui/common/components-helpers';
|
|
4
|
+
import { compose } from '@descope-ui/common/utils';
|
|
5
|
+
|
|
6
|
+
// NOTICE: This component registers with a DIFFERENT name than its file name
|
|
7
|
+
export const componentName = getComponentName('ponyhot');
|
|
8
|
+
|
|
9
|
+
const HP_PREFIX = 'dhp-';
|
|
10
|
+
|
|
11
|
+
const BaseClass = createBaseClass({
|
|
12
|
+
componentName,
|
|
13
|
+
baseSelector: '',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
class RawHoneypot extends BaseClass {
|
|
17
|
+
get type() {
|
|
18
|
+
return this.getAttribute('type');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get value() {
|
|
22
|
+
return this.input?.value || '';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
init() {
|
|
26
|
+
this.input = this.querySelector('input');
|
|
27
|
+
|
|
28
|
+
if (!this.input) return;
|
|
29
|
+
|
|
30
|
+
this.#setupTrapListeners();
|
|
31
|
+
this.#setupValueInterceptor();
|
|
32
|
+
this.#hideComponent();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#setupTrapListeners() {
|
|
36
|
+
const events = [
|
|
37
|
+
'input',
|
|
38
|
+
'change',
|
|
39
|
+
'click',
|
|
40
|
+
'keydown',
|
|
41
|
+
'keyup',
|
|
42
|
+
'mouseover',
|
|
43
|
+
'mousedown',
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const fillHoneypot = (e) => {
|
|
47
|
+
this.input.value = `${HP_PREFIX}${e.type}`;
|
|
48
|
+
|
|
49
|
+
// Stop listening to all events after first trigger
|
|
50
|
+
events.forEach((event) => {
|
|
51
|
+
this.input.removeEventListener(event, fillHoneypot);
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
events.forEach((event) => {
|
|
56
|
+
this.input.addEventListener(event, fillHoneypot);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Intercept direct value assignments to catch bots that bypass events
|
|
61
|
+
#setupValueInterceptor() {
|
|
62
|
+
const originalDescriptor = Object.getOwnPropertyDescriptor(
|
|
63
|
+
HTMLInputElement.prototype,
|
|
64
|
+
'value',
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
Object.defineProperty(this.input, 'value', {
|
|
68
|
+
get() {
|
|
69
|
+
return originalDescriptor.get.call(this);
|
|
70
|
+
},
|
|
71
|
+
set(newValue) {
|
|
72
|
+
const currentValue = originalDescriptor.get.call(this);
|
|
73
|
+
|
|
74
|
+
// Only trap if the value is being set by external code (not our own trap)
|
|
75
|
+
if (
|
|
76
|
+
newValue &&
|
|
77
|
+
!newValue.startsWith(HP_PREFIX) &&
|
|
78
|
+
newValue !== currentValue
|
|
79
|
+
) {
|
|
80
|
+
originalDescriptor.set.call(this, `${HP_PREFIX}direct-set`);
|
|
81
|
+
} else {
|
|
82
|
+
originalDescriptor.set.call(this, newValue);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
configurable: true,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#hideComponent() {
|
|
90
|
+
const styles = {
|
|
91
|
+
position: 'absolute',
|
|
92
|
+
opacity: '0.01',
|
|
93
|
+
width: '1px',
|
|
94
|
+
height: '1px',
|
|
95
|
+
pointerEvents: 'none',
|
|
96
|
+
zIndex: '-1',
|
|
97
|
+
left: '-999999px',
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
Object.entries(styles).forEach(([key, value]) => {
|
|
101
|
+
this.style[key] = value;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const HoneypotClass = compose(componentNameValidationMixin)(RawHoneypot);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { componentName } from '../src/component';
|
|
2
|
+
|
|
3
|
+
const Template = ({ name, type }) => `
|
|
4
|
+
<descope-ponyhot>
|
|
5
|
+
<input
|
|
6
|
+
name="${name || 'required-descope-validation'}"
|
|
7
|
+
type="${type || 'text'}"
|
|
8
|
+
autocomplete="off"
|
|
9
|
+
tabindex="-1"
|
|
10
|
+
aria-hidden="true"
|
|
11
|
+
data-1p-ignore
|
|
12
|
+
data-lpignore="true"
|
|
13
|
+
data-bwignore
|
|
14
|
+
data-form-type="other"
|
|
15
|
+
data-protonpass-ignore
|
|
16
|
+
/>
|
|
17
|
+
</descope-ponyhot>
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
export default {
|
|
21
|
+
component: componentName,
|
|
22
|
+
title: 'descope-honeypot',
|
|
23
|
+
parameters: {
|
|
24
|
+
panelPosition: 'right',
|
|
25
|
+
controls: { expanded: true },
|
|
26
|
+
},
|
|
27
|
+
argTypes: {
|
|
28
|
+
type: {
|
|
29
|
+
control: { type: 'select' },
|
|
30
|
+
options: ['text', 'email', 'password', 'address'],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const Default = Template.bind({});
|
|
36
|
+
|
|
37
|
+
Default.args = {
|
|
38
|
+
name: 'text',
|
|
39
|
+
type: 'text',
|
|
40
|
+
};
|