secure-ui-components 0.1.1 → 0.1.3
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/README.md +368 -38
- package/dist/components/secure-card/secure-card.css +427 -0
- package/dist/components/secure-card/secure-card.d.ts +73 -0
- package/dist/components/secure-card/secure-card.d.ts.map +1 -0
- package/dist/components/secure-card/secure-card.js +787 -0
- package/dist/components/secure-card/secure-card.js.map +1 -0
- package/dist/components/secure-datetime/secure-datetime.d.ts.map +1 -1
- package/dist/components/secure-datetime/secure-datetime.js +13 -22
- package/dist/components/secure-datetime/secure-datetime.js.map +1 -1
- package/dist/components/secure-file-upload/secure-file-upload.d.ts.map +1 -1
- package/dist/components/secure-file-upload/secure-file-upload.js +6 -17
- package/dist/components/secure-file-upload/secure-file-upload.js.map +1 -1
- package/dist/components/secure-form/secure-form.d.ts.map +1 -1
- package/dist/components/secure-form/secure-form.js +133 -16
- package/dist/components/secure-form/secure-form.js.map +1 -1
- package/dist/components/secure-input/secure-input.d.ts.map +1 -1
- package/dist/components/secure-input/secure-input.js +31 -26
- package/dist/components/secure-input/secure-input.js.map +1 -1
- package/dist/components/secure-select/secure-select.d.ts.map +1 -1
- package/dist/components/secure-select/secure-select.js +12 -20
- package/dist/components/secure-select/secure-select.js.map +1 -1
- package/dist/components/secure-submit-button/secure-submit-button.js +1 -1
- package/dist/components/secure-submit-button/secure-submit-button.js.map +1 -1
- package/dist/components/secure-table/secure-table.d.ts.map +1 -1
- package/dist/components/secure-table/secure-table.js +17 -16
- package/dist/components/secure-table/secure-table.js.map +1 -1
- package/dist/components/secure-telemetry-provider/secure-telemetry-provider.d.ts +63 -0
- package/dist/components/secure-telemetry-provider/secure-telemetry-provider.d.ts.map +1 -0
- package/dist/components/secure-telemetry-provider/secure-telemetry-provider.js +198 -0
- package/dist/components/secure-telemetry-provider/secure-telemetry-provider.js.map +1 -0
- package/dist/components/secure-textarea/secure-textarea.d.ts.map +1 -1
- package/dist/components/secure-textarea/secure-textarea.js +12 -20
- package/dist/components/secure-textarea/secure-textarea.js.map +1 -1
- package/dist/core/base-component.d.ts +23 -1
- package/dist/core/base-component.d.ts.map +1 -1
- package/dist/core/base-component.js +111 -0
- package/dist/core/base-component.js.map +1 -1
- package/dist/core/security-config.d.ts.map +1 -1
- package/dist/core/security-config.js +5 -21
- package/dist/core/security-config.js.map +1 -1
- package/dist/core/types.d.ts +129 -8
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/package.json +4 -2
- package/package.json +13 -1
package/README.md
CHANGED
|
@@ -1,19 +1,96 @@
|
|
|
1
1
|
# secure-ui-components
|
|
2
2
|
|
|
3
|
-
Security-first Web Component library with
|
|
3
|
+
Security-first Web Component library with built-in behavioral telemetry. Zero dependencies.
|
|
4
4
|
|
|
5
5
|
**[Live Demo](https://barryprender.github.io/Secure-UI/)** — Try all components in your browser.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **
|
|
9
|
+
- **10 Secure Components** — Input, Textarea, Select, Form, File Upload, DateTime, Table, Submit Button, Card, Telemetry Provider
|
|
10
10
|
- **4-Tier Security System** — `public`, `authenticated`, `sensitive`, `critical`
|
|
11
|
+
- **Behavioral Telemetry** — Every field collects typing patterns, paste detection, dwell time, and correction signals automatically
|
|
12
|
+
- **Risk Scoring** — `<secure-form>` aggregates field signals into a session-level risk score at submission
|
|
13
|
+
- **Signed Envelopes** — `<secure-telemetry-provider>` detects automation/headless browsers and signs every submission with HMAC-SHA-256
|
|
11
14
|
- **Zero Dependencies** — Pure TypeScript, no runtime dependencies
|
|
12
15
|
- **Progressive Enhancement** — All components render meaningful markup and work without JavaScript
|
|
13
16
|
- **CSP-Safe** — Styles loaded via `<link>` from `'self'`; no `unsafe-inline` required
|
|
14
17
|
- **SSR Friendly** — Adopts server-rendered markup on upgrade; no document access in constructors
|
|
15
18
|
- **Fully Customisable** — CSS Design Tokens + `::part()` API
|
|
16
|
-
- **Comprehensive Testing** —
|
|
19
|
+
- **Comprehensive Testing** — 869 tests, 80%+ branch coverage
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Philosophy: Security Telemetry as a First-Class Primitive
|
|
24
|
+
|
|
25
|
+
Traditional form security stops at validation and CSRF protection. Secure-UI goes further — every form submission carries a behavioral fingerprint that travels alongside the user's data, giving the server the context it needs to distinguish real users from bots and credential stuffers in a single atomic request.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
Field interaction → Behavioral signals → Risk score → Signed envelope
|
|
29
|
+
(SecureBaseComponent) (SecureForm) (SecureForm) (SecureTelemetryProvider)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Layer 1 — Field-level signals** (`SecureBaseComponent`)
|
|
33
|
+
Every secure field silently records: dwell time from focus to first keystroke, typing velocity, correction count (backspace/delete), paste detection, autofill detection, focus count, and blur-without-change patterns.
|
|
34
|
+
|
|
35
|
+
**Layer 2 — Session aggregation** (`<secure-form>`)
|
|
36
|
+
At submission, the form queries `getFieldTelemetry()` from every child field, produces per-field snapshots, and computes a composite risk score from 0–100. The telemetry payload travels alongside form data in a single `fetch` request as `_telemetry`.
|
|
37
|
+
|
|
38
|
+
**Layer 3 — Environmental signals** (`<secure-telemetry-provider>`)
|
|
39
|
+
An optional overlay that wraps `<secure-form>`. Monitors for WebDriver/headless flags, DOM script injection (via `MutationObserver`), devtools, suspicious screen dimensions, and pointer/keyboard activity. Signs the final envelope with HMAC-SHA-256 so the server can detect replay attacks.
|
|
40
|
+
|
|
41
|
+
**What the server receives (enhanced submission):**
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"email": "user@example.com",
|
|
46
|
+
"password": "...",
|
|
47
|
+
"_telemetry": {
|
|
48
|
+
"sessionDuration": 14320,
|
|
49
|
+
"fieldCount": 2,
|
|
50
|
+
"riskScore": 5,
|
|
51
|
+
"riskSignals": [],
|
|
52
|
+
"submittedAt": "2026-03-12T18:30:00.000Z",
|
|
53
|
+
"fields": [
|
|
54
|
+
{
|
|
55
|
+
"fieldName": "email",
|
|
56
|
+
"fieldType": "secure-input",
|
|
57
|
+
"dwell": 420,
|
|
58
|
+
"completionTime": 3100,
|
|
59
|
+
"velocity": 4.2,
|
|
60
|
+
"corrections": 1,
|
|
61
|
+
"pasteDetected": false,
|
|
62
|
+
"autofillDetected": false,
|
|
63
|
+
"focusCount": 1,
|
|
64
|
+
"blurWithoutChange": 0
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"_env": {
|
|
68
|
+
"nonce": "a3f9...",
|
|
69
|
+
"issuedAt": "2026-03-12T18:30:00.000Z",
|
|
70
|
+
"environment": {
|
|
71
|
+
"webdriverDetected": false,
|
|
72
|
+
"headlessDetected": false,
|
|
73
|
+
"mouseMovementDetected": true,
|
|
74
|
+
"pointerType": "mouse"
|
|
75
|
+
},
|
|
76
|
+
"signature": "7d3a..."
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Risk signals**
|
|
83
|
+
|
|
84
|
+
| Signal | Condition | Score |
|
|
85
|
+
|--------|-----------|-------|
|
|
86
|
+
| `session_too_fast` | Submitted in under 3 s | +30 |
|
|
87
|
+
| `session_fast` | Submitted in under 8 s | +10 |
|
|
88
|
+
| `all_fields_pasted` | All fields pasted, no keystrokes | +25 |
|
|
89
|
+
| `field_filled_without_focus` | Any field has `focusCount = 0` | +15 |
|
|
90
|
+
| `high_velocity_typing` | Any field velocity > 15 ks/s | +15 |
|
|
91
|
+
| `form_probing` | Field focused/blurred > 1× with no input | +10 |
|
|
92
|
+
| `high_correction_count` | Any field with > 5 corrections | +5 |
|
|
93
|
+
| `autofill_detected` | All fields autofilled (trust signal) | −10 |
|
|
17
94
|
|
|
18
95
|
---
|
|
19
96
|
|
|
@@ -27,21 +104,32 @@ npm install secure-ui-components
|
|
|
27
104
|
|
|
28
105
|
## Quick Start
|
|
29
106
|
|
|
107
|
+
### With telemetry (recommended)
|
|
108
|
+
|
|
109
|
+
```html
|
|
110
|
+
<secure-telemetry-provider signing-key="your-per-session-secret">
|
|
111
|
+
<secure-form action="/api/login" method="POST" csrf-token="..." enhance>
|
|
112
|
+
<secure-input label="Email" name="email" type="email" required security-tier="authenticated"></secure-input>
|
|
113
|
+
<secure-input label="Password" name="password" type="password" required security-tier="critical"></secure-input>
|
|
114
|
+
<secure-submit-button label="Sign in" loading-label="Signing in…"></secure-submit-button>
|
|
115
|
+
</secure-form>
|
|
116
|
+
</secure-telemetry-provider>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
document.querySelector('secure-form').addEventListener('secure-form-submit', (e) => {
|
|
121
|
+
const { formData, telemetry } = e.detail;
|
|
122
|
+
console.log('Risk score:', telemetry.riskScore);
|
|
123
|
+
console.log('Risk signals:', telemetry.riskSignals);
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
30
127
|
### Bundler (Vite, Webpack, Rollup)
|
|
31
128
|
|
|
32
129
|
```js
|
|
33
130
|
import 'secure-ui-components/secure-input';
|
|
34
131
|
import 'secure-ui-components/secure-form';
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
```html
|
|
38
|
-
<secure-input
|
|
39
|
-
label="Email"
|
|
40
|
-
name="email"
|
|
41
|
-
type="email"
|
|
42
|
-
required
|
|
43
|
-
security-tier="authenticated"
|
|
44
|
-
></secure-input>
|
|
132
|
+
import 'secure-ui-components/secure-telemetry-provider';
|
|
45
133
|
```
|
|
46
134
|
|
|
47
135
|
### CDN / Vanilla HTML
|
|
@@ -89,7 +177,7 @@ The `security-tier` attribute is **immutable after connection** — changes afte
|
|
|
89
177
|
|
|
90
178
|
### `<secure-input>`
|
|
91
179
|
|
|
92
|
-
Text input with XSS prevention, masking, password strength validation, and
|
|
180
|
+
Text input with XSS prevention, masking, password strength validation, rate limiting, and automatic telemetry collection.
|
|
93
181
|
|
|
94
182
|
**Attributes**
|
|
95
183
|
|
|
@@ -114,12 +202,13 @@ Text input with XSS prevention, masking, password strength validation, and rate
|
|
|
114
202
|
```js
|
|
115
203
|
const el = document.querySelector('secure-input');
|
|
116
204
|
|
|
117
|
-
el.value
|
|
118
|
-
el.value = 'foo'
|
|
119
|
-
el.valid
|
|
120
|
-
el.name
|
|
121
|
-
el.getAuditLog()
|
|
205
|
+
el.value // get current value (unmasked)
|
|
206
|
+
el.value = 'foo' // set value programmatically
|
|
207
|
+
el.valid // boolean — passes all validation rules
|
|
208
|
+
el.name // field name string
|
|
209
|
+
el.getAuditLog() // AuditLogEntry[]
|
|
122
210
|
el.clearAuditLog()
|
|
211
|
+
el.getFieldTelemetry() // FieldTelemetry — behavioral signals for this field
|
|
123
212
|
el.focus()
|
|
124
213
|
el.blur()
|
|
125
214
|
```
|
|
@@ -147,11 +236,11 @@ el.blur()
|
|
|
147
236
|
|
|
148
237
|
### `<secure-textarea>`
|
|
149
238
|
|
|
150
|
-
Multi-line input with real-time character counter and
|
|
239
|
+
Multi-line input with real-time character counter, rate limiting, and automatic telemetry collection.
|
|
151
240
|
|
|
152
241
|
**Attributes:** `label`, `name`, `placeholder`, `required`, `disabled`, `readonly`, `minlength`, `maxlength`, `rows`, `cols`, `wrap`, `value`, `security-tier`
|
|
153
242
|
|
|
154
|
-
**Properties & Methods:** `value`, `name`, `valid`, `getAuditLog()`, `clearAuditLog()`, `focus()`, `blur()`
|
|
243
|
+
**Properties & Methods:** `value`, `name`, `valid`, `getAuditLog()`, `clearAuditLog()`, `getFieldTelemetry()`, `focus()`, `blur()`
|
|
155
244
|
|
|
156
245
|
**Events:** `secure-textarea` → `{ name, value, tier }`
|
|
157
246
|
|
|
@@ -168,7 +257,7 @@ Multi-line input with real-time character counter and rate limiting.
|
|
|
168
257
|
|
|
169
258
|
### `<secure-select>`
|
|
170
259
|
|
|
171
|
-
Dropdown with option whitelist validation — prevents value injection.
|
|
260
|
+
Dropdown with option whitelist validation — prevents value injection. Telemetry collected on `change` events.
|
|
172
261
|
|
|
173
262
|
**Attributes:** `label`, `name`, `required`, `disabled`, `multiple`, `size`, `value`, `security-tier`
|
|
174
263
|
|
|
@@ -183,6 +272,7 @@ el.addOption(value, text, selected?)
|
|
|
183
272
|
el.removeOption(value)
|
|
184
273
|
el.clearOptions()
|
|
185
274
|
el.getAuditLog()
|
|
275
|
+
el.getFieldTelemetry()
|
|
186
276
|
el.focus()
|
|
187
277
|
el.blur()
|
|
188
278
|
```
|
|
@@ -197,13 +287,13 @@ el.blur()
|
|
|
197
287
|
</secure-select>
|
|
198
288
|
```
|
|
199
289
|
|
|
200
|
-
Light DOM `<option>` children are transferred to the shadow DOM automatically. Only values added via `<option>` or `addOption()` are accepted
|
|
290
|
+
Light DOM `<option>` children are transferred to the shadow DOM automatically. Only values added via `<option>` or `addOption()` are accepted.
|
|
201
291
|
|
|
202
292
|
---
|
|
203
293
|
|
|
204
294
|
### `<secure-form>`
|
|
205
295
|
|
|
206
|
-
Form container with CSRF protection, field validation, and optional fetch-enhanced submission.
|
|
296
|
+
Form container with CSRF protection, field validation, behavioral telemetry aggregation, and optional fetch-enhanced submission.
|
|
207
297
|
|
|
208
298
|
> `<secure-form>` uses **light DOM** (no Shadow DOM) for native form submission compatibility.
|
|
209
299
|
|
|
@@ -228,20 +318,33 @@ el.valid // true if all secure child fields pass validation
|
|
|
228
318
|
el.securityTier
|
|
229
319
|
el.getData() // { fieldName: value, … } including CSRF token
|
|
230
320
|
el.reset()
|
|
231
|
-
el.submit() // programmatic submit (triggers validation)
|
|
321
|
+
el.submit() // programmatic submit (triggers validation + telemetry)
|
|
232
322
|
```
|
|
233
323
|
|
|
234
324
|
**Events**
|
|
235
325
|
|
|
236
326
|
| Event | Detail |
|
|
237
327
|
|-------|--------|
|
|
238
|
-
| `secure-form-submit` | `{ formData, formElement, preventDefault() }` — cancelable |
|
|
239
|
-
| `secure-form-success` | `{ formData, response }` — only
|
|
328
|
+
| `secure-form-submit` | `{ formData, formElement, telemetry, preventDefault() }` — cancelable |
|
|
329
|
+
| `secure-form-success` | `{ formData, response, telemetry }` — only when `enhance` is set |
|
|
330
|
+
|
|
331
|
+
**`telemetry` shape** (`SessionTelemetry`):
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
{
|
|
335
|
+
sessionDuration: number; // ms from form mount to submission
|
|
336
|
+
fieldCount: number;
|
|
337
|
+
fields: FieldTelemetrySnapshot[];
|
|
338
|
+
riskScore: number; // 0–100
|
|
339
|
+
riskSignals: string[]; // e.g. ['session_too_fast', 'all_fields_pasted']
|
|
340
|
+
submittedAt: string; // ISO 8601
|
|
341
|
+
}
|
|
342
|
+
```
|
|
240
343
|
|
|
241
344
|
**Submission modes**
|
|
242
345
|
|
|
243
|
-
- **Without `enhance`** — native browser form submission. Values from shadow DOM inputs are synced to hidden `<input type="hidden">` fields automatically.
|
|
244
|
-
- **With `enhance`** — intercepts submit, validates all fields, sends JSON via `fetch`.
|
|
346
|
+
- **Without `enhance`** — native browser form submission. Values from shadow DOM inputs are synced to hidden `<input type="hidden">` fields automatically. Telemetry is available in `secure-form-submit` but not sent to the server.
|
|
347
|
+
- **With `enhance`** — intercepts submit, validates all fields, sends `{ ...formData, _telemetry }` as JSON via `fetch`. Full telemetry payload travels to the server in the same request.
|
|
245
348
|
|
|
246
349
|
**Example**
|
|
247
350
|
|
|
@@ -253,6 +356,111 @@ el.submit() // programmatic submit (triggers validation)
|
|
|
253
356
|
</secure-form>
|
|
254
357
|
```
|
|
255
358
|
|
|
359
|
+
```js
|
|
360
|
+
form.addEventListener('secure-form-submit', (e) => {
|
|
361
|
+
const { formData, telemetry } = e.detail;
|
|
362
|
+
|
|
363
|
+
// Block high-risk submissions before they reach your server
|
|
364
|
+
if (telemetry.riskScore >= 50) {
|
|
365
|
+
e.detail.preventDefault();
|
|
366
|
+
showChallenge();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Log signals for your fraud pipeline
|
|
371
|
+
analytics.track('form_submit', {
|
|
372
|
+
risk: telemetry.riskScore,
|
|
373
|
+
signals: telemetry.riskSignals,
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
### `<secure-telemetry-provider>`
|
|
381
|
+
|
|
382
|
+
Optional overlay that wraps `<secure-form>` to add environmental signals and HMAC-SHA-256 signing to every submission envelope.
|
|
383
|
+
|
|
384
|
+
> Place this as the outer wrapper. It monitors the entire document for automation markers and DOM tampering during the session.
|
|
385
|
+
|
|
386
|
+
**Attributes**
|
|
387
|
+
|
|
388
|
+
| Attribute | Description |
|
|
389
|
+
|-----------|-------------|
|
|
390
|
+
| `signing-key` | HMAC-SHA-256 key — must also be known server-side to verify the signature |
|
|
391
|
+
|
|
392
|
+
**Properties & Methods**
|
|
393
|
+
|
|
394
|
+
```js
|
|
395
|
+
const provider = document.querySelector('secure-telemetry-provider');
|
|
396
|
+
|
|
397
|
+
provider.collectSignals() // EnvironmentalSignals — point-in-time snapshot
|
|
398
|
+
provider.getEnvironmentalSignals() // alias for collectSignals()
|
|
399
|
+
provider.sign(signals) // Promise<SignedTelemetryEnvelope>
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**What it detects**
|
|
403
|
+
|
|
404
|
+
| Signal | Description |
|
|
405
|
+
|--------|-------------|
|
|
406
|
+
| `webdriverDetected` | `navigator.webdriver` present or truthy |
|
|
407
|
+
| `headlessDetected` | `HeadlessChrome` in userAgent or missing `window.chrome` |
|
|
408
|
+
| `domMutationDetected` | New `<script>` element injected after page load |
|
|
409
|
+
| `injectedScriptCount` | Count of dynamically added `<script>` elements |
|
|
410
|
+
| `devtoolsOpen` | `outerWidth − innerWidth > 160` or `outerHeight − innerHeight > 160` |
|
|
411
|
+
| `suspiciousScreenSize` | Screen width or height is zero or < 100px |
|
|
412
|
+
| `pointerType` | Last pointer event type: `mouse` \| `touch` \| `pen` \| `none` |
|
|
413
|
+
| `mouseMovementDetected` | Any `mousemove` event fired during session |
|
|
414
|
+
| `keyboardActivityDetected` | Any `keydown` event fired during session |
|
|
415
|
+
|
|
416
|
+
**How the envelope is injected**
|
|
417
|
+
|
|
418
|
+
The provider listens for `secure-form-submit` on itself (bubbles from the nested form). It calls `sign()` asynchronously and attaches the result as `detail.telemetry._env`. Since it mutates the same object reference, any handler that awaits a microtask after the event fires will see `_env` populated.
|
|
419
|
+
|
|
420
|
+
**Signed envelope shape** (`SignedTelemetryEnvelope`):
|
|
421
|
+
|
|
422
|
+
```ts
|
|
423
|
+
{
|
|
424
|
+
nonce: string; // 32-char random hex — detect replays
|
|
425
|
+
issuedAt: string; // ISO 8601
|
|
426
|
+
environment: EnvironmentalSignals;
|
|
427
|
+
signature: string; // HMAC-SHA-256 hex over nonce.issuedAt.JSON(environment)
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
**Security notes**
|
|
432
|
+
|
|
433
|
+
- The `signing-key` is a *symmetric* secret. For strong guarantees, rotate it per-session via a server nonce endpoint rather than hardcoding it as an attribute.
|
|
434
|
+
- All signals are heuristic — a determined attacker can spoof them. The value is raising the cost of scripted attacks.
|
|
435
|
+
- In non-secure contexts (`http://`) `SubtleCrypto` is unavailable; the signature will be an empty string. The server should treat unsigned envelopes with reduced trust.
|
|
436
|
+
|
|
437
|
+
**Example**
|
|
438
|
+
|
|
439
|
+
```html
|
|
440
|
+
<secure-telemetry-provider signing-key="per-session-server-issued-key">
|
|
441
|
+
<secure-form action="/api/login" enhance csrf-token="...">
|
|
442
|
+
<secure-input label="Email" name="email" type="email" required></secure-input>
|
|
443
|
+
<secure-input label="Password" name="password" type="password" required security-tier="critical"></secure-input>
|
|
444
|
+
<secure-submit-button label="Sign in"></secure-submit-button>
|
|
445
|
+
</secure-form>
|
|
446
|
+
</secure-telemetry-provider>
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
```js
|
|
450
|
+
document.querySelector('secure-form').addEventListener('secure-form-submit', async (e) => {
|
|
451
|
+
const { telemetry } = e.detail;
|
|
452
|
+
|
|
453
|
+
// _env is populated async by the provider — wait a microtask
|
|
454
|
+
await Promise.resolve();
|
|
455
|
+
|
|
456
|
+
if (telemetry._env) {
|
|
457
|
+
console.log('Nonce:', telemetry._env.nonce);
|
|
458
|
+
console.log('Signature:', telemetry._env.signature);
|
|
459
|
+
// Verify signature server-side with the same key
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
```
|
|
463
|
+
|
|
256
464
|
---
|
|
257
465
|
|
|
258
466
|
### `<secure-file-upload>`
|
|
@@ -352,6 +560,7 @@ el.name
|
|
|
352
560
|
el.getValueAsDate() // Date | null
|
|
353
561
|
el.setValueFromDate(date) // accepts Date object, sets formatted value
|
|
354
562
|
el.getAuditLog()
|
|
563
|
+
el.getFieldTelemetry()
|
|
355
564
|
el.focus()
|
|
356
565
|
el.blur()
|
|
357
566
|
```
|
|
@@ -425,6 +634,106 @@ table.columns = [
|
|
|
425
634
|
|
|
426
635
|
---
|
|
427
636
|
|
|
637
|
+
### `<secure-card>`
|
|
638
|
+
|
|
639
|
+
Composite credit card form with a live 3D card preview, automatic card type detection, Luhn validation, expiry checking, and aggregate telemetry across all four fields. All inputs render inside a single closed Shadow DOM.
|
|
640
|
+
|
|
641
|
+
**Security model:**
|
|
642
|
+
- Full PAN and CVC are never included in events, audit logs, or hidden form inputs
|
|
643
|
+
- CVC uses native `type="password"` masking — never visible on screen
|
|
644
|
+
- Card number is masked to last-4 on blur
|
|
645
|
+
- Security tier is locked to `critical` and cannot be changed
|
|
646
|
+
- All sensitive state is wiped on `disconnectedCallback`
|
|
647
|
+
- Telemetry from all four inputs (number, expiry, CVC, name) is aggregated into one composite behavioral fingerprint
|
|
648
|
+
|
|
649
|
+
> Raw card data must be passed directly to a PCI-compliant payment processor SDK (e.g. Stripe.js, Braintree). Use `getCardData()` for that handoff — never send raw card numbers or CVCs to your own server.
|
|
650
|
+
|
|
651
|
+
**Attributes**
|
|
652
|
+
|
|
653
|
+
| Attribute | Type | Description |
|
|
654
|
+
|-----------|------|-------------|
|
|
655
|
+
| `label` | string | Legend text displayed above the fields |
|
|
656
|
+
| `name` | string | Base name for hidden form inputs |
|
|
657
|
+
| `show-name` | boolean | Show the optional cardholder name field |
|
|
658
|
+
| `disabled` | boolean | Disable all fields |
|
|
659
|
+
| `required` | boolean | Mark fields as required |
|
|
660
|
+
|
|
661
|
+
**Properties & Methods**
|
|
662
|
+
|
|
663
|
+
```js
|
|
664
|
+
const card = document.querySelector('secure-card');
|
|
665
|
+
|
|
666
|
+
card.valid // true when all visible fields pass validation
|
|
667
|
+
card.cardType // 'visa' | 'mastercard' | 'amex' | 'discover' | 'diners' | 'jcb' | 'unknown'
|
|
668
|
+
card.last4 // last 4 digits — safe to display and log
|
|
669
|
+
card.name // value of the name attribute
|
|
670
|
+
card.getCardData() // { number, expiry, cvc, name } | null — for payment SDK only
|
|
671
|
+
card.getFieldTelemetry() // composite behavioral signals across all 4 card inputs
|
|
672
|
+
card.reset()
|
|
673
|
+
card.focus()
|
|
674
|
+
card.getAuditLog()
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
**Events**
|
|
678
|
+
|
|
679
|
+
| Event | Detail |
|
|
680
|
+
|-------|--------|
|
|
681
|
+
| `secure-card` | `{ name, cardType, last4, expiryMonth, expiryYear, cardholderName, valid, tier }` |
|
|
682
|
+
| `secure-audit` | `{ event, tier, timestamp, … }` |
|
|
683
|
+
|
|
684
|
+
Note: the `secure-card` event detail intentionally omits the full PAN and CVC.
|
|
685
|
+
|
|
686
|
+
**CSS Parts**
|
|
687
|
+
|
|
688
|
+
| Part | Element |
|
|
689
|
+
|------|---------|
|
|
690
|
+
| `container` | Outer wrapper |
|
|
691
|
+
| `label` | Legend element |
|
|
692
|
+
| `wrapper` | Input wrapper (per field group) |
|
|
693
|
+
| `number-input` | Card number `<input>` |
|
|
694
|
+
| `expiry-input` | Expiry `<input>` |
|
|
695
|
+
| `cvc-input` | CVC `<input>` |
|
|
696
|
+
| `name-input` | Cardholder name `<input>` |
|
|
697
|
+
| `error` | Error message container (per field) |
|
|
698
|
+
|
|
699
|
+
**Card type detection**
|
|
700
|
+
|
|
701
|
+
| Type | Detected prefix |
|
|
702
|
+
|------|----------------|
|
|
703
|
+
| Visa | `4` |
|
|
704
|
+
| Mastercard | `51–55`, `2221–2720` |
|
|
705
|
+
| Amex | `34`, `37` |
|
|
706
|
+
| Discover | `6011`, `65xx` |
|
|
707
|
+
| Diners | `300–305`, `36`, `38` |
|
|
708
|
+
| JCB | `2131`, `1800`, `35xxx` |
|
|
709
|
+
|
|
710
|
+
**Form participation**
|
|
711
|
+
|
|
712
|
+
Three hidden inputs are created in the light DOM:
|
|
713
|
+
- `{name}` — last 4 digits only (not full PAN)
|
|
714
|
+
- `{name}-expiry` — MM/YY string
|
|
715
|
+
- `{name}-holder` — cardholder name
|
|
716
|
+
|
|
717
|
+
No hidden input is created for CVC.
|
|
718
|
+
|
|
719
|
+
**Example**
|
|
720
|
+
|
|
721
|
+
```html
|
|
722
|
+
<secure-card name="payment" label="Card details" show-name></secure-card>
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
```js
|
|
726
|
+
// When the user clicks Pay — pass directly to your payment SDK
|
|
727
|
+
payButton.addEventListener('click', async () => {
|
|
728
|
+
const data = card.getCardData();
|
|
729
|
+
if (!data) return;
|
|
730
|
+
const token = await stripe.createToken({ number: data.number, exp_month: ..., cvc: data.cvc });
|
|
731
|
+
// Send token.id to your server — never data.number or data.cvc
|
|
732
|
+
});
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
428
737
|
### `<secure-submit-button>`
|
|
429
738
|
|
|
430
739
|
Accessible submit button with loading state and automatic form-validity gating.
|
|
@@ -453,7 +762,7 @@ el.getAuditLog()
|
|
|
453
762
|
|
|
454
763
|
## Common Attributes
|
|
455
764
|
|
|
456
|
-
All components support:
|
|
765
|
+
All field components support:
|
|
457
766
|
|
|
458
767
|
| Attribute | Type | Description |
|
|
459
768
|
|-----------|------|-------------|
|
|
@@ -466,17 +775,35 @@ All components support:
|
|
|
466
775
|
|
|
467
776
|
## Common Properties & Methods
|
|
468
777
|
|
|
778
|
+
All field components expose these in addition to component-specific methods:
|
|
779
|
+
|
|
469
780
|
```js
|
|
470
|
-
el.value
|
|
471
|
-
el.valid
|
|
472
|
-
el.name
|
|
473
|
-
el.securityTier
|
|
474
|
-
el.getAuditLog()
|
|
781
|
+
el.value // get/set current value
|
|
782
|
+
el.valid // boolean — passes all validation rules
|
|
783
|
+
el.name // field name string
|
|
784
|
+
el.securityTier // current security tier
|
|
785
|
+
el.getAuditLog() // AuditLogEntry[]
|
|
475
786
|
el.clearAuditLog()
|
|
787
|
+
el.getFieldTelemetry() // FieldTelemetry — behavioral signals (no raw values)
|
|
476
788
|
el.focus()
|
|
477
789
|
el.blur()
|
|
478
790
|
```
|
|
479
791
|
|
|
792
|
+
**`FieldTelemetry` shape:**
|
|
793
|
+
|
|
794
|
+
```ts
|
|
795
|
+
{
|
|
796
|
+
dwell: number; // ms from focus to first keystroke
|
|
797
|
+
completionTime: number; // ms from first keystroke to blur
|
|
798
|
+
velocity: number; // keystrokes per second
|
|
799
|
+
corrections: number; // backspace / delete event count
|
|
800
|
+
pasteDetected: boolean;
|
|
801
|
+
autofillDetected: boolean;
|
|
802
|
+
focusCount: number;
|
|
803
|
+
blurWithoutChange: number; // focused but left without typing
|
|
804
|
+
}
|
|
805
|
+
```
|
|
806
|
+
|
|
480
807
|
## Common Events
|
|
481
808
|
|
|
482
809
|
| Event | Fired by | Detail |
|
|
@@ -486,8 +813,9 @@ el.blur()
|
|
|
486
813
|
| `secure-select` | `<secure-select>` | `{ name, value, tier }` |
|
|
487
814
|
| `secure-datetime` | `<secure-datetime>` | `{ name, value, type, tier }` |
|
|
488
815
|
| `secure-file-upload` | `<secure-file-upload>` | `{ name, files, tier }` |
|
|
489
|
-
| `secure-form-submit` | `<secure-form>` | `{ formData, formElement, preventDefault() }` |
|
|
490
|
-
| `secure-form-success` | `<secure-form>` | `{ formData, response }` |
|
|
816
|
+
| `secure-form-submit` | `<secure-form>` | `{ formData, formElement, telemetry, preventDefault() }` |
|
|
817
|
+
| `secure-form-success` | `<secure-form>` | `{ formData, response, telemetry }` |
|
|
818
|
+
| `secure-card` | `<secure-card>` | `{ name, cardType, last4, expiryMonth, expiryYear, cardholderName, valid, tier }` |
|
|
491
819
|
| `secure-audit` | all components | `{ event, tier, timestamp, … }` |
|
|
492
820
|
| `table-action` | `<secure-table>` | `{ action, row }` |
|
|
493
821
|
|
|
@@ -527,7 +855,7 @@ secure-input::part(error) {
|
|
|
527
855
|
}
|
|
528
856
|
```
|
|
529
857
|
|
|
530
|
-
**Available parts on all components:** `container`, `label`, `wrapper`, `input` / `textarea` / `select`, `error
|
|
858
|
+
**Available parts on all components:** `container`, `label`, `wrapper`, `input` / `textarea` / `select`, `error`
|
|
531
859
|
|
|
532
860
|
See the [Customization Guide](https://github.com/Barryprender/Secure-UI/blob/main/secure-ui-components/docs/customization.md) for a full token reference.
|
|
533
861
|
|
|
@@ -545,6 +873,8 @@ import 'secure-ui-components/secure-form';
|
|
|
545
873
|
import 'secure-ui-components/secure-file-upload';
|
|
546
874
|
import 'secure-ui-components/secure-datetime';
|
|
547
875
|
import 'secure-ui-components/secure-table';
|
|
876
|
+
import 'secure-ui-components/secure-card';
|
|
877
|
+
import 'secure-ui-components/secure-telemetry-provider';
|
|
548
878
|
```
|
|
549
879
|
|
|
550
880
|
Or import everything at once:
|