secure-ui-components 0.1.0-beta.1
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/LICENSE +21 -0
- package/README.md +310 -0
- package/dist/components/secure-datetime/secure-datetime.css +263 -0
- package/dist/components/secure-datetime/secure-datetime.d.ts +124 -0
- package/dist/components/secure-datetime/secure-datetime.d.ts.map +1 -0
- package/dist/components/secure-datetime/secure-datetime.js +610 -0
- package/dist/components/secure-datetime/secure-datetime.js.map +1 -0
- package/dist/components/secure-file-upload/secure-file-upload.css +334 -0
- package/dist/components/secure-file-upload/secure-file-upload.d.ts +150 -0
- package/dist/components/secure-file-upload/secure-file-upload.d.ts.map +1 -0
- package/dist/components/secure-file-upload/secure-file-upload.js +911 -0
- package/dist/components/secure-file-upload/secure-file-upload.js.map +1 -0
- package/dist/components/secure-form/secure-form.css +62 -0
- package/dist/components/secure-form/secure-form.d.ts +128 -0
- package/dist/components/secure-form/secure-form.d.ts.map +1 -0
- package/dist/components/secure-form/secure-form.js +697 -0
- package/dist/components/secure-form/secure-form.js.map +1 -0
- package/dist/components/secure-input/secure-input.css +168 -0
- package/dist/components/secure-input/secure-input.d.ts +114 -0
- package/dist/components/secure-input/secure-input.d.ts.map +1 -0
- package/dist/components/secure-input/secure-input.js +785 -0
- package/dist/components/secure-input/secure-input.js.map +1 -0
- package/dist/components/secure-select/secure-select.css +195 -0
- package/dist/components/secure-select/secure-select.d.ts +149 -0
- package/dist/components/secure-select/secure-select.d.ts.map +1 -0
- package/dist/components/secure-select/secure-select.js +634 -0
- package/dist/components/secure-select/secure-select.js.map +1 -0
- package/dist/components/secure-submit-button/secure-submit-button.css +135 -0
- package/dist/components/secure-submit-button/secure-submit-button.d.ts +61 -0
- package/dist/components/secure-submit-button/secure-submit-button.d.ts.map +1 -0
- package/dist/components/secure-submit-button/secure-submit-button.js +399 -0
- package/dist/components/secure-submit-button/secure-submit-button.js.map +1 -0
- package/dist/components/secure-table/secure-table.css +341 -0
- package/dist/components/secure-table/secure-table.d.ts +64 -0
- package/dist/components/secure-table/secure-table.d.ts.map +1 -0
- package/dist/components/secure-table/secure-table.js +567 -0
- package/dist/components/secure-table/secure-table.js.map +1 -0
- package/dist/components/secure-textarea/secure-textarea.css +153 -0
- package/dist/components/secure-textarea/secure-textarea.d.ts +111 -0
- package/dist/components/secure-textarea/secure-textarea.d.ts.map +1 -0
- package/dist/components/secure-textarea/secure-textarea.js +477 -0
- package/dist/components/secure-textarea/secure-textarea.js.map +1 -0
- package/dist/core/base-component.d.ts +134 -0
- package/dist/core/base-component.d.ts.map +1 -0
- package/dist/core/base-component.js +303 -0
- package/dist/core/base-component.js.map +1 -0
- package/dist/core/base.css +37 -0
- package/dist/core/security-config.d.ts +89 -0
- package/dist/core/security-config.d.ts.map +1 -0
- package/dist/core/security-config.js +273 -0
- package/dist/core/security-config.js.map +1 -0
- package/dist/core/types.d.ts +212 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +7 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/package.json +89 -0
- package/dist/styles/tokens.css +257 -0
- package/package.json +118 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Secure Textarea Component
|
|
3
|
+
*
|
|
4
|
+
* A security-first textarea component that implements progressive enhancement,
|
|
5
|
+
* tier-based validation, character counting, and audit logging.
|
|
6
|
+
*
|
|
7
|
+
* Progressive Enhancement Strategy:
|
|
8
|
+
* 1. Without JavaScript: Falls back to native HTML5 textarea with attributes
|
|
9
|
+
* 2. With JavaScript: Enhances with real-time validation, character limits, rate limiting
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* <secure-textarea
|
|
13
|
+
* security-tier="sensitive"
|
|
14
|
+
* name="bio"
|
|
15
|
+
* label="Biography"
|
|
16
|
+
* rows="5"
|
|
17
|
+
* required
|
|
18
|
+
* ></secure-textarea>
|
|
19
|
+
*
|
|
20
|
+
* Security Features:
|
|
21
|
+
* - XSS prevention via sanitization
|
|
22
|
+
* - Character counting and limits based on security tier
|
|
23
|
+
* - Rate limiting for sensitive/critical tiers
|
|
24
|
+
* - Autocomplete control based on tier
|
|
25
|
+
* - Comprehensive audit logging
|
|
26
|
+
* - Visual security indicators
|
|
27
|
+
*
|
|
28
|
+
* @module secure-textarea
|
|
29
|
+
* @license MIT
|
|
30
|
+
*/
|
|
31
|
+
import { SecureBaseComponent } from '../../core/base-component.js';
|
|
32
|
+
/**
|
|
33
|
+
* Secure Textarea Web Component
|
|
34
|
+
*
|
|
35
|
+
* Provides a security-hardened textarea field with progressive enhancement.
|
|
36
|
+
* The component works as a standard form textarea without JavaScript and
|
|
37
|
+
* enhances with security features when JavaScript is available.
|
|
38
|
+
*
|
|
39
|
+
* @extends SecureBaseComponent
|
|
40
|
+
*/
|
|
41
|
+
export class SecureTextarea extends SecureBaseComponent {
|
|
42
|
+
/**
|
|
43
|
+
* Textarea element reference
|
|
44
|
+
* @private
|
|
45
|
+
*/
|
|
46
|
+
#textareaElement = null;
|
|
47
|
+
/**
|
|
48
|
+
* Label element reference
|
|
49
|
+
* @private
|
|
50
|
+
*/
|
|
51
|
+
#labelElement = null;
|
|
52
|
+
/**
|
|
53
|
+
* Error container element reference
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
#errorContainer = null;
|
|
57
|
+
/**
|
|
58
|
+
* Character count display element
|
|
59
|
+
* @private
|
|
60
|
+
*/
|
|
61
|
+
#charCountElement = null;
|
|
62
|
+
/**
|
|
63
|
+
* Unique ID for this textarea instance
|
|
64
|
+
* @private
|
|
65
|
+
*/
|
|
66
|
+
#instanceId = `secure-textarea-${Math.random().toString(36).substr(2, 9)}`;
|
|
67
|
+
/**
|
|
68
|
+
* Observed attributes for this component
|
|
69
|
+
*
|
|
70
|
+
* @static
|
|
71
|
+
*/
|
|
72
|
+
static get observedAttributes() {
|
|
73
|
+
return [
|
|
74
|
+
...super.observedAttributes,
|
|
75
|
+
'name',
|
|
76
|
+
'label',
|
|
77
|
+
'placeholder',
|
|
78
|
+
'required',
|
|
79
|
+
'minlength',
|
|
80
|
+
'maxlength',
|
|
81
|
+
'rows',
|
|
82
|
+
'cols',
|
|
83
|
+
'wrap',
|
|
84
|
+
'value'
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Constructor
|
|
89
|
+
*/
|
|
90
|
+
constructor() {
|
|
91
|
+
super();
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Render the textarea component
|
|
95
|
+
*
|
|
96
|
+
* Security Note: We use a native <textarea> element wrapped in our web component
|
|
97
|
+
* to ensure progressive enhancement. The native textarea works without JavaScript,
|
|
98
|
+
* and we enhance it with security features when JS is available.
|
|
99
|
+
*
|
|
100
|
+
* @protected
|
|
101
|
+
*/
|
|
102
|
+
render() {
|
|
103
|
+
const fragment = document.createDocumentFragment();
|
|
104
|
+
const config = this.config;
|
|
105
|
+
// Create container
|
|
106
|
+
const container = document.createElement('div');
|
|
107
|
+
container.className = 'textarea-container';
|
|
108
|
+
// Create label
|
|
109
|
+
const label = this.getAttribute('label');
|
|
110
|
+
if (label) {
|
|
111
|
+
this.#labelElement = document.createElement('label');
|
|
112
|
+
this.#labelElement.htmlFor = this.#instanceId;
|
|
113
|
+
this.#labelElement.textContent = this.sanitizeValue(label);
|
|
114
|
+
// Add security tier suffix if configured
|
|
115
|
+
if (config.ui.labelSuffix) {
|
|
116
|
+
const suffix = document.createElement('span');
|
|
117
|
+
suffix.className = 'label-suffix';
|
|
118
|
+
suffix.textContent = config.ui.labelSuffix;
|
|
119
|
+
this.#labelElement.appendChild(suffix);
|
|
120
|
+
}
|
|
121
|
+
// Add security badge if configured
|
|
122
|
+
if (config.ui.showSecurityBadge) {
|
|
123
|
+
const badge = document.createElement('span');
|
|
124
|
+
badge.className = 'security-badge';
|
|
125
|
+
badge.textContent = config.name;
|
|
126
|
+
this.#labelElement.appendChild(badge);
|
|
127
|
+
}
|
|
128
|
+
container.appendChild(this.#labelElement);
|
|
129
|
+
}
|
|
130
|
+
// Create textarea wrapper for progressive enhancement
|
|
131
|
+
const textareaWrapper = document.createElement('div');
|
|
132
|
+
textareaWrapper.className = 'textarea-wrapper';
|
|
133
|
+
// Create the actual textarea element
|
|
134
|
+
this.#textareaElement = document.createElement('textarea');
|
|
135
|
+
this.#textareaElement.id = this.#instanceId;
|
|
136
|
+
this.#textareaElement.className = 'textarea-field';
|
|
137
|
+
// Apply attributes from web component to native textarea
|
|
138
|
+
this.#applyTextareaAttributes();
|
|
139
|
+
// Set up event listeners
|
|
140
|
+
this.#attachEventListeners();
|
|
141
|
+
textareaWrapper.appendChild(this.#textareaElement);
|
|
142
|
+
container.appendChild(textareaWrapper);
|
|
143
|
+
// Create character count display
|
|
144
|
+
this.#charCountElement = document.createElement('span');
|
|
145
|
+
this.#charCountElement.className = 'char-count';
|
|
146
|
+
this.#updateCharCount();
|
|
147
|
+
container.appendChild(this.#charCountElement);
|
|
148
|
+
// Create error container
|
|
149
|
+
// role="alert" already implies aria-live="assertive" — do not override with polite
|
|
150
|
+
this.#errorContainer = document.createElement('div');
|
|
151
|
+
this.#errorContainer.className = 'error-container hidden';
|
|
152
|
+
this.#errorContainer.setAttribute('role', 'alert');
|
|
153
|
+
this.#errorContainer.id = `${this.#instanceId}-error`;
|
|
154
|
+
container.appendChild(this.#errorContainer);
|
|
155
|
+
// Add component styles (CSP-compliant via adoptedStyleSheets)
|
|
156
|
+
this.addComponentStyles(this.#getComponentStyles());
|
|
157
|
+
fragment.appendChild(container);
|
|
158
|
+
return fragment;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Apply attributes from the web component to the native textarea
|
|
162
|
+
*
|
|
163
|
+
* Security Note: This is where we enforce tier-specific security controls
|
|
164
|
+
* like autocomplete, caching, and validation rules.
|
|
165
|
+
*
|
|
166
|
+
* @private
|
|
167
|
+
*/
|
|
168
|
+
#applyTextareaAttributes() {
|
|
169
|
+
const config = this.config;
|
|
170
|
+
// Name attribute (required for form submission)
|
|
171
|
+
const name = this.getAttribute('name');
|
|
172
|
+
if (name) {
|
|
173
|
+
this.#textareaElement.name = this.sanitizeValue(name);
|
|
174
|
+
}
|
|
175
|
+
// Accessible name fallback when no visible label is provided
|
|
176
|
+
if (!this.getAttribute('label') && name) {
|
|
177
|
+
this.#textareaElement.setAttribute('aria-label', this.sanitizeValue(name));
|
|
178
|
+
}
|
|
179
|
+
// Link textarea to its error container for screen readers
|
|
180
|
+
this.#textareaElement.setAttribute('aria-describedby', `${this.#instanceId}-error`);
|
|
181
|
+
// Placeholder
|
|
182
|
+
const placeholder = this.getAttribute('placeholder');
|
|
183
|
+
if (placeholder) {
|
|
184
|
+
this.#textareaElement.placeholder = this.sanitizeValue(placeholder);
|
|
185
|
+
}
|
|
186
|
+
// Required attribute
|
|
187
|
+
if (this.hasAttribute('required') || config.validation.required) {
|
|
188
|
+
this.#textareaElement.required = true;
|
|
189
|
+
this.#textareaElement.setAttribute('aria-required', 'true');
|
|
190
|
+
}
|
|
191
|
+
// Length constraints
|
|
192
|
+
const minLength = this.getAttribute('minlength');
|
|
193
|
+
if (minLength) {
|
|
194
|
+
this.#textareaElement.minLength = parseInt(minLength, 10);
|
|
195
|
+
}
|
|
196
|
+
const maxLength = this.getAttribute('maxlength') || config.validation.maxLength;
|
|
197
|
+
if (maxLength) {
|
|
198
|
+
this.#textareaElement.maxLength = parseInt(String(maxLength), 10);
|
|
199
|
+
}
|
|
200
|
+
// Rows and columns
|
|
201
|
+
const rows = this.getAttribute('rows') || 3;
|
|
202
|
+
this.#textareaElement.rows = parseInt(String(rows), 10);
|
|
203
|
+
const cols = this.getAttribute('cols');
|
|
204
|
+
if (cols) {
|
|
205
|
+
this.#textareaElement.cols = parseInt(cols, 10);
|
|
206
|
+
}
|
|
207
|
+
// Wrap attribute
|
|
208
|
+
const wrap = this.getAttribute('wrap') || 'soft';
|
|
209
|
+
this.#textareaElement.wrap = wrap;
|
|
210
|
+
// CRITICAL SECURITY: Autocomplete control based on tier
|
|
211
|
+
// For SENSITIVE and CRITICAL tiers, we disable autocomplete to prevent
|
|
212
|
+
// browser storage of sensitive data
|
|
213
|
+
if (config.storage.allowAutocomplete) {
|
|
214
|
+
const autocomplete = this.getAttribute('autocomplete') || 'on';
|
|
215
|
+
this.#textareaElement.autocomplete = autocomplete;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
this.#textareaElement.autocomplete = 'off';
|
|
219
|
+
}
|
|
220
|
+
// Disabled state
|
|
221
|
+
if (this.hasAttribute('disabled')) {
|
|
222
|
+
this.#textareaElement.disabled = true;
|
|
223
|
+
}
|
|
224
|
+
// Readonly state
|
|
225
|
+
if (this.hasAttribute('readonly')) {
|
|
226
|
+
this.#textareaElement.readOnly = true;
|
|
227
|
+
}
|
|
228
|
+
// Initial value
|
|
229
|
+
const value = this.getAttribute('value');
|
|
230
|
+
if (value) {
|
|
231
|
+
this.#textareaElement.value = value;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Attach event listeners to the textarea
|
|
236
|
+
*
|
|
237
|
+
* @private
|
|
238
|
+
*/
|
|
239
|
+
#attachEventListeners() {
|
|
240
|
+
// Focus event - audit logging
|
|
241
|
+
this.#textareaElement.addEventListener('focus', () => {
|
|
242
|
+
this.audit('textarea_focused', {
|
|
243
|
+
name: this.#textareaElement.name
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
// Input event - real-time validation and character counting
|
|
247
|
+
this.#textareaElement.addEventListener('input', (e) => {
|
|
248
|
+
this.#handleInput(e);
|
|
249
|
+
});
|
|
250
|
+
// Blur event - final validation
|
|
251
|
+
this.#textareaElement.addEventListener('blur', () => {
|
|
252
|
+
this.#validateAndShowErrors();
|
|
253
|
+
this.audit('textarea_blurred', {
|
|
254
|
+
name: this.#textareaElement.name,
|
|
255
|
+
hasValue: this.#textareaElement.value.length > 0
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
// Change event - audit logging
|
|
259
|
+
this.#textareaElement.addEventListener('change', () => {
|
|
260
|
+
this.audit('textarea_changed', {
|
|
261
|
+
name: this.#textareaElement.name,
|
|
262
|
+
valueLength: this.#textareaElement.value.length
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Handle input events
|
|
268
|
+
*
|
|
269
|
+
* Security Note: This is where we implement real-time validation and character counting.
|
|
270
|
+
*
|
|
271
|
+
* @private
|
|
272
|
+
*/
|
|
273
|
+
#handleInput(_event) {
|
|
274
|
+
// Update character count
|
|
275
|
+
this.#updateCharCount();
|
|
276
|
+
// Clear previous errors on input (improve UX)
|
|
277
|
+
this.#clearErrors();
|
|
278
|
+
// Dispatch custom event for parent forms
|
|
279
|
+
this.dispatchEvent(new CustomEvent('secure-textarea', {
|
|
280
|
+
detail: {
|
|
281
|
+
name: this.#textareaElement.name,
|
|
282
|
+
value: this.#textareaElement.value,
|
|
283
|
+
tier: this.securityTier
|
|
284
|
+
},
|
|
285
|
+
bubbles: true,
|
|
286
|
+
composed: true
|
|
287
|
+
}));
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Update character count display
|
|
291
|
+
*
|
|
292
|
+
* @private
|
|
293
|
+
*/
|
|
294
|
+
#updateCharCount() {
|
|
295
|
+
const currentLength = this.#textareaElement.value.length;
|
|
296
|
+
const maxLength = this.#textareaElement.maxLength;
|
|
297
|
+
if (maxLength > 0) {
|
|
298
|
+
this.#charCountElement.textContent = `${currentLength} / ${maxLength}`;
|
|
299
|
+
// Warn when approaching limit
|
|
300
|
+
if (currentLength > maxLength * 0.9) {
|
|
301
|
+
this.#charCountElement.classList.add('warning');
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
this.#charCountElement.classList.remove('warning');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
this.#charCountElement.textContent = `${currentLength}`;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Validate the textarea and show error messages
|
|
313
|
+
*
|
|
314
|
+
* @private
|
|
315
|
+
*/
|
|
316
|
+
#validateAndShowErrors() {
|
|
317
|
+
// Check rate limit first
|
|
318
|
+
const rateLimitCheck = this.checkRateLimit();
|
|
319
|
+
if (!rateLimitCheck.allowed) {
|
|
320
|
+
this.#showError(`Too many attempts. Please wait ${Math.ceil(rateLimitCheck.retryAfter / 1000)} seconds.`);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
// Perform validation
|
|
324
|
+
const minLength = this.getAttribute('minlength');
|
|
325
|
+
const maxLength = this.getAttribute('maxlength');
|
|
326
|
+
const validation = this.validateInput(this.#textareaElement.value, {
|
|
327
|
+
minLength: minLength ? parseInt(minLength, 10) : 0,
|
|
328
|
+
maxLength: maxLength ? parseInt(maxLength, 10) : this.config.validation.maxLength
|
|
329
|
+
});
|
|
330
|
+
if (!validation.valid) {
|
|
331
|
+
this.#showError(validation.errors.join(', '));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Show error message
|
|
336
|
+
*
|
|
337
|
+
* @private
|
|
338
|
+
*/
|
|
339
|
+
#showError(message) {
|
|
340
|
+
this.#errorContainer.textContent = message;
|
|
341
|
+
// Force reflow so browser registers the hidden state with content,
|
|
342
|
+
// then remove hidden to trigger the CSS transition
|
|
343
|
+
void this.#errorContainer.offsetHeight;
|
|
344
|
+
this.#errorContainer.classList.remove('hidden');
|
|
345
|
+
this.#textareaElement.classList.add('error');
|
|
346
|
+
this.#textareaElement.setAttribute('aria-invalid', 'true');
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Clear error messages
|
|
350
|
+
*
|
|
351
|
+
* @private
|
|
352
|
+
*/
|
|
353
|
+
#clearErrors() {
|
|
354
|
+
// Start the hide animation first, clear text only after transition ends
|
|
355
|
+
this.#errorContainer.classList.add('hidden');
|
|
356
|
+
this.#errorContainer.addEventListener('transitionend', () => {
|
|
357
|
+
if (this.#errorContainer.classList.contains('hidden')) {
|
|
358
|
+
this.#errorContainer.textContent = '';
|
|
359
|
+
}
|
|
360
|
+
}, { once: true });
|
|
361
|
+
this.#textareaElement.classList.remove('error');
|
|
362
|
+
this.#textareaElement.removeAttribute('aria-invalid');
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Get component-specific styles
|
|
366
|
+
*
|
|
367
|
+
* @private
|
|
368
|
+
*/
|
|
369
|
+
#getComponentStyles() {
|
|
370
|
+
return new URL('./secure-textarea.css', import.meta.url).href;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Handle attribute changes
|
|
374
|
+
*
|
|
375
|
+
* @protected
|
|
376
|
+
*/
|
|
377
|
+
handleAttributeChange(name, _oldValue, newValue) {
|
|
378
|
+
if (!this.#textareaElement)
|
|
379
|
+
return;
|
|
380
|
+
switch (name) {
|
|
381
|
+
case 'disabled':
|
|
382
|
+
this.#textareaElement.disabled = this.hasAttribute('disabled');
|
|
383
|
+
break;
|
|
384
|
+
case 'readonly':
|
|
385
|
+
this.#textareaElement.readOnly = this.hasAttribute('readonly');
|
|
386
|
+
break;
|
|
387
|
+
case 'value':
|
|
388
|
+
if (newValue !== this.#textareaElement.value) {
|
|
389
|
+
this.#textareaElement.value = newValue || '';
|
|
390
|
+
this.#updateCharCount();
|
|
391
|
+
}
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Get the current value
|
|
397
|
+
*
|
|
398
|
+
* @public
|
|
399
|
+
*/
|
|
400
|
+
get value() {
|
|
401
|
+
return this.#textareaElement ? this.#textareaElement.value : '';
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Set the value
|
|
405
|
+
*
|
|
406
|
+
* @public
|
|
407
|
+
*/
|
|
408
|
+
set value(value) {
|
|
409
|
+
if (this.#textareaElement) {
|
|
410
|
+
this.#textareaElement.value = value || '';
|
|
411
|
+
this.#updateCharCount();
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Get the textarea name
|
|
416
|
+
*
|
|
417
|
+
* @public
|
|
418
|
+
*/
|
|
419
|
+
get name() {
|
|
420
|
+
return this.#textareaElement ? this.#textareaElement.name : '';
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Check if the textarea is valid
|
|
424
|
+
*
|
|
425
|
+
* @public
|
|
426
|
+
*/
|
|
427
|
+
get valid() {
|
|
428
|
+
const minLength = this.getAttribute('minlength');
|
|
429
|
+
const maxLength = this.getAttribute('maxlength');
|
|
430
|
+
const required = this.hasAttribute('required');
|
|
431
|
+
// Check required field first
|
|
432
|
+
if (required || this.config.validation.required) {
|
|
433
|
+
if (!this.#textareaElement.value || this.#textareaElement.value.trim().length === 0) {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const validation = this.validateInput(this.#textareaElement.value, {
|
|
438
|
+
minLength: minLength ? parseInt(minLength, 10) : 0,
|
|
439
|
+
maxLength: maxLength ? parseInt(maxLength, 10) : this.config.validation.maxLength
|
|
440
|
+
});
|
|
441
|
+
return validation.valid;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Focus the textarea
|
|
445
|
+
*
|
|
446
|
+
* @public
|
|
447
|
+
*/
|
|
448
|
+
focus() {
|
|
449
|
+
if (this.#textareaElement) {
|
|
450
|
+
this.#textareaElement.focus();
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Blur the textarea
|
|
455
|
+
*
|
|
456
|
+
* @public
|
|
457
|
+
*/
|
|
458
|
+
blur() {
|
|
459
|
+
if (this.#textareaElement) {
|
|
460
|
+
this.#textareaElement.blur();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Cleanup on disconnect
|
|
465
|
+
*/
|
|
466
|
+
disconnectedCallback() {
|
|
467
|
+
super.disconnectedCallback();
|
|
468
|
+
// Clear sensitive data from memory
|
|
469
|
+
if (this.#textareaElement) {
|
|
470
|
+
this.#textareaElement.value = '';
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
// Define the custom element
|
|
475
|
+
customElements.define('secure-textarea', SecureTextarea);
|
|
476
|
+
export default SecureTextarea;
|
|
477
|
+
//# sourceMappingURL=secure-textarea.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secure-textarea.js","sourceRoot":"","sources":["../../../src/components/secure-textarea/secure-textarea.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Base Component Class for Secure-UI
|
|
3
|
+
*
|
|
4
|
+
* This module provides the foundational class that all Secure-UI web components
|
|
5
|
+
* extend. It implements core security features, progressive enhancement,
|
|
6
|
+
* and standardized lifecycle management.
|
|
7
|
+
*
|
|
8
|
+
* Security Features:
|
|
9
|
+
* - Closed Shadow DOM (prevents external JavaScript access)
|
|
10
|
+
* - Automatic XSS sanitization
|
|
11
|
+
* - Security tier enforcement
|
|
12
|
+
* - Audit logging infrastructure
|
|
13
|
+
* - Rate limiting support
|
|
14
|
+
* - Progressive enhancement (works without JS)
|
|
15
|
+
*
|
|
16
|
+
* @module base-component
|
|
17
|
+
* @license MIT
|
|
18
|
+
*/
|
|
19
|
+
import type { SecurityTierValue, TierConfig, ValidationResult, ValidationOptions, RateLimitResult, AuditLogEntry } from './types.js';
|
|
20
|
+
/**
|
|
21
|
+
* Base class for all Secure-UI components
|
|
22
|
+
*
|
|
23
|
+
* All components in the Secure-UI library should extend this class to inherit
|
|
24
|
+
* core security functionality and standardized behavior.
|
|
25
|
+
*
|
|
26
|
+
* Security Architecture:
|
|
27
|
+
* - Closed Shadow DOM prevents external tampering
|
|
28
|
+
* - All attributes are sanitized on read
|
|
29
|
+
* - Security tier is immutable after initial set
|
|
30
|
+
* - Default tier is CRITICAL (fail secure)
|
|
31
|
+
*/
|
|
32
|
+
export declare abstract class SecureBaseComponent extends HTMLElement {
|
|
33
|
+
#private;
|
|
34
|
+
/**
|
|
35
|
+
* Constructor
|
|
36
|
+
*
|
|
37
|
+
* Security Note: Creates a CLOSED shadow DOM to prevent external JavaScript
|
|
38
|
+
* from accessing or modifying the component's internal DOM.
|
|
39
|
+
*/
|
|
40
|
+
constructor();
|
|
41
|
+
/**
|
|
42
|
+
* Observed attributes - must be overridden by child classes
|
|
43
|
+
*/
|
|
44
|
+
static get observedAttributes(): string[];
|
|
45
|
+
/**
|
|
46
|
+
* Called when element is added to DOM
|
|
47
|
+
*/
|
|
48
|
+
connectedCallback(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Initialize security tier, config, and audit logging without triggering render.
|
|
51
|
+
*
|
|
52
|
+
* Components that manage their own rendering (e.g. secure-table) can call this
|
|
53
|
+
* from their connectedCallback instead of super.connectedCallback() to get
|
|
54
|
+
* security initialization without the base render lifecycle.
|
|
55
|
+
* @protected
|
|
56
|
+
*/
|
|
57
|
+
protected initializeSecurity(): void;
|
|
58
|
+
/**
|
|
59
|
+
* Called when an observed attribute changes
|
|
60
|
+
*
|
|
61
|
+
* Security Note: security-tier attribute is immutable after initialization
|
|
62
|
+
* to prevent privilege escalation.
|
|
63
|
+
*/
|
|
64
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
65
|
+
/**
|
|
66
|
+
* Handle attribute changes - to be overridden by child classes
|
|
67
|
+
*/
|
|
68
|
+
protected handleAttributeChange(_name: string, _oldValue: string | null, _newValue: string | null): void;
|
|
69
|
+
/**
|
|
70
|
+
* Returns the URL of the shared base stylesheet.
|
|
71
|
+
* Components that manage their own rendering (e.g. secure-table) can use
|
|
72
|
+
* this to inject the base <link> themselves.
|
|
73
|
+
* @protected
|
|
74
|
+
*/
|
|
75
|
+
protected getBaseStylesheetUrl(): string;
|
|
76
|
+
/**
|
|
77
|
+
* Inject a component stylesheet into the shadow root via <link>.
|
|
78
|
+
* Accepts a URL (use import.meta.url to derive it, e.g.
|
|
79
|
+
* new URL('./my-component.css', import.meta.url).href
|
|
80
|
+
* ). Loading from 'self' satisfies strict CSP without unsafe-inline.
|
|
81
|
+
* @protected
|
|
82
|
+
*/
|
|
83
|
+
protected addComponentStyles(cssUrl: string): void;
|
|
84
|
+
/**
|
|
85
|
+
* Render method to be implemented by child classes
|
|
86
|
+
*/
|
|
87
|
+
protected abstract render(): DocumentFragment | HTMLElement | null;
|
|
88
|
+
/**
|
|
89
|
+
* Sanitize a string value to prevent XSS
|
|
90
|
+
*/
|
|
91
|
+
protected sanitizeValue(value: string): string;
|
|
92
|
+
/**
|
|
93
|
+
* Validate input against tier-specific rules
|
|
94
|
+
*/
|
|
95
|
+
protected validateInput(value: string, options?: ValidationOptions): ValidationResult;
|
|
96
|
+
/**
|
|
97
|
+
* Check rate limit for this component
|
|
98
|
+
*/
|
|
99
|
+
protected checkRateLimit(): RateLimitResult;
|
|
100
|
+
/**
|
|
101
|
+
* Get the shadow root (protected access for child classes)
|
|
102
|
+
*/
|
|
103
|
+
get shadowRoot(): ShadowRoot;
|
|
104
|
+
/**
|
|
105
|
+
* Get the current security tier
|
|
106
|
+
*/
|
|
107
|
+
get securityTier(): SecurityTierValue;
|
|
108
|
+
/**
|
|
109
|
+
* Get the tier configuration
|
|
110
|
+
*/
|
|
111
|
+
get config(): TierConfig;
|
|
112
|
+
/**
|
|
113
|
+
* Get all audit log entries
|
|
114
|
+
*/
|
|
115
|
+
getAuditLog(): AuditLogEntry[];
|
|
116
|
+
/**
|
|
117
|
+
* Clear the local audit log
|
|
118
|
+
*/
|
|
119
|
+
clearAuditLog(): void;
|
|
120
|
+
/**
|
|
121
|
+
* Trigger an audit event from child classes
|
|
122
|
+
*/
|
|
123
|
+
protected audit(event: string, data: Record<string, unknown>): void;
|
|
124
|
+
/**
|
|
125
|
+
* Force re-render of the component
|
|
126
|
+
*/
|
|
127
|
+
protected rerender(): void;
|
|
128
|
+
/**
|
|
129
|
+
* Clean up when component is removed from DOM
|
|
130
|
+
*/
|
|
131
|
+
disconnectedCallback(): void;
|
|
132
|
+
}
|
|
133
|
+
export default SecureBaseComponent;
|
|
134
|
+
//# sourceMappingURL=base-component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-component.d.ts","sourceRoot":"","sources":["../../src/core/base-component.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAQH,OAAO,KAAK,EACV,iBAAiB,EACjB,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EAEf,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;GAWG;AACH,8BAAsB,mBAAoB,SAAQ,WAAW;;IAW3D;;;;;OAKG;;IAOH;;OAEG;IACH,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAED;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAYzB;;;;;;;OAOG;IACH,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAcpC;;;;;OAKG;IACH,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAiB9F;;OAEG;IACH,SAAS,CAAC,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAsBxG;;;;;OAKG;IACH,SAAS,CAAC,oBAAoB,IAAI,MAAM;IAIxC;;;;;;OAMG;IACH,SAAS,CAAC,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAOlD;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,MAAM,IAAI,gBAAgB,GAAG,WAAW,GAAG,IAAI;IAElE;;OAEG;IACH,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAU9C;;OAEG;IACH,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,gBAAgB;IAoCzF;;OAEG;IACH,SAAS,CAAC,cAAc,IAAI,eAAe;IAiE3C;;OAEG;IACH,IAAI,UAAU,IAAI,UAAU,CAE3B;IAED;;OAEG;IACH,IAAI,YAAY,IAAI,iBAAiB,CAEpC;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,UAAU,CAEvB;IAED;;OAEG;IACH,WAAW,IAAI,aAAa,EAAE;IAI9B;;OAEG;IACH,aAAa,IAAI,IAAI;IAIrB;;OAEG;IACH,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAInE;;OAEG;IACH,SAAS,CAAC,QAAQ,IAAI,IAAI;IAI1B;;OAEG;IACH,oBAAoB,IAAI,IAAI;CAS7B;AAED,eAAe,mBAAmB,CAAC"}
|