@autumnsgrove/groveengine 0.1.0
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 +163 -0
- package/dist/auth/jwt.d.ts +14 -0
- package/dist/auth/jwt.js +109 -0
- package/dist/auth/session.d.ts +42 -0
- package/dist/auth/session.js +105 -0
- package/dist/components/admin/GutterManager.svelte +910 -0
- package/dist/components/admin/GutterManager.svelte.d.ts +15 -0
- package/dist/components/admin/MarkdownEditor.svelte +3114 -0
- package/dist/components/admin/MarkdownEditor.svelte.d.ts +43 -0
- package/dist/components/custom/CollapsibleSection.svelte +74 -0
- package/dist/components/custom/CollapsibleSection.svelte.d.ts +15 -0
- package/dist/components/custom/ContentWithGutter.svelte +646 -0
- package/dist/components/custom/ContentWithGutter.svelte.d.ts +19 -0
- package/dist/components/custom/GutterItem.svelte +201 -0
- package/dist/components/custom/GutterItem.svelte.d.ts +11 -0
- package/dist/components/custom/LeftGutter.svelte +271 -0
- package/dist/components/custom/LeftGutter.svelte.d.ts +17 -0
- package/dist/components/custom/MobileTOC.svelte +273 -0
- package/dist/components/custom/MobileTOC.svelte.d.ts +11 -0
- package/dist/components/custom/TableOfContents.svelte +163 -0
- package/dist/components/custom/TableOfContents.svelte.d.ts +11 -0
- package/dist/components/gallery/ImageGallery.svelte +681 -0
- package/dist/components/gallery/ImageGallery.svelte.d.ts +11 -0
- package/dist/components/gallery/Lightbox.svelte +107 -0
- package/dist/components/gallery/Lightbox.svelte.d.ts +19 -0
- package/dist/components/gallery/LightboxCaption.svelte +25 -0
- package/dist/components/gallery/LightboxCaption.svelte.d.ts +11 -0
- package/dist/components/gallery/ZoomableImage.svelte +163 -0
- package/dist/components/gallery/ZoomableImage.svelte.d.ts +17 -0
- package/dist/components/ui/Accordion.svelte +74 -0
- package/dist/components/ui/Accordion.svelte.d.ts +42 -0
- package/dist/components/ui/Badge.svelte +48 -0
- package/dist/components/ui/Badge.svelte.d.ts +26 -0
- package/dist/components/ui/Button.svelte +74 -0
- package/dist/components/ui/Button.svelte.d.ts +34 -0
- package/dist/components/ui/Card.svelte +102 -0
- package/dist/components/ui/Card.svelte.d.ts +46 -0
- package/dist/components/ui/Dialog.svelte +91 -0
- package/dist/components/ui/Dialog.svelte.d.ts +43 -0
- package/dist/components/ui/Input.svelte +81 -0
- package/dist/components/ui/Input.svelte.d.ts +35 -0
- package/dist/components/ui/Select.svelte +69 -0
- package/dist/components/ui/Select.svelte.d.ts +36 -0
- package/dist/components/ui/Sheet.svelte +98 -0
- package/dist/components/ui/Sheet.svelte.d.ts +45 -0
- package/dist/components/ui/Skeleton.svelte +31 -0
- package/dist/components/ui/Skeleton.svelte.d.ts +26 -0
- package/dist/components/ui/Table.svelte +59 -0
- package/dist/components/ui/Table.svelte.d.ts +44 -0
- package/dist/components/ui/Tabs.svelte +76 -0
- package/dist/components/ui/Tabs.svelte.d.ts +41 -0
- package/dist/components/ui/Textarea.svelte +81 -0
- package/dist/components/ui/Textarea.svelte.d.ts +35 -0
- package/dist/components/ui/Toast.svelte +18 -0
- package/dist/components/ui/Toast.svelte.d.ts +7 -0
- package/dist/components/ui/accordion/accordion-content.svelte +24 -0
- package/dist/components/ui/accordion/accordion-content.svelte.d.ts +4 -0
- package/dist/components/ui/accordion/accordion-item.svelte +12 -0
- package/dist/components/ui/accordion/accordion-item.svelte.d.ts +4 -0
- package/dist/components/ui/accordion/accordion-trigger.svelte +29 -0
- package/dist/components/ui/accordion/accordion-trigger.svelte.d.ts +7 -0
- package/dist/components/ui/accordion/index.d.ts +6 -0
- package/dist/components/ui/accordion/index.js +8 -0
- package/dist/components/ui/badge/badge.svelte +50 -0
- package/dist/components/ui/badge/badge.svelte.d.ts +60 -0
- package/dist/components/ui/badge/index.d.ts +2 -0
- package/dist/components/ui/badge/index.js +2 -0
- package/dist/components/ui/button/button.svelte +82 -0
- package/dist/components/ui/button/button.svelte.d.ts +132 -0
- package/dist/components/ui/button/index.d.ts +2 -0
- package/dist/components/ui/button/index.js +4 -0
- package/dist/components/ui/card/card-content.svelte +16 -0
- package/dist/components/ui/card/card-content.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-description.svelte +16 -0
- package/dist/components/ui/card/card-description.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-footer.svelte +16 -0
- package/dist/components/ui/card/card-footer.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-header.svelte +16 -0
- package/dist/components/ui/card/card-header.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-title.svelte +25 -0
- package/dist/components/ui/card/card-title.svelte.d.ts +8 -0
- package/dist/components/ui/card/card.svelte +20 -0
- package/dist/components/ui/card/card.svelte.d.ts +5 -0
- package/dist/components/ui/card/index.d.ts +7 -0
- package/dist/components/ui/card/index.js +9 -0
- package/dist/components/ui/dialog/dialog-content.svelte +38 -0
- package/dist/components/ui/dialog/dialog-content.svelte.d.ts +9 -0
- package/dist/components/ui/dialog/dialog-description.svelte +16 -0
- package/dist/components/ui/dialog/dialog-description.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/dialog-footer.svelte +20 -0
- package/dist/components/ui/dialog/dialog-footer.svelte.d.ts +5 -0
- package/dist/components/ui/dialog/dialog-header.svelte +20 -0
- package/dist/components/ui/dialog/dialog-header.svelte.d.ts +5 -0
- package/dist/components/ui/dialog/dialog-overlay.svelte +19 -0
- package/dist/components/ui/dialog/dialog-overlay.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/dialog-title.svelte +16 -0
- package/dist/components/ui/dialog/dialog-title.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/index.d.ts +12 -0
- package/dist/components/ui/dialog/index.js +14 -0
- package/dist/components/ui/index.d.ts +26 -0
- package/dist/components/ui/index.js +29 -0
- package/dist/components/ui/input/index.d.ts +2 -0
- package/dist/components/ui/input/index.js +4 -0
- package/dist/components/ui/input/input.svelte +46 -0
- package/dist/components/ui/input/input.svelte.d.ts +13 -0
- package/dist/components/ui/select/index.d.ts +11 -0
- package/dist/components/ui/select/index.js +13 -0
- package/dist/components/ui/select/select-content.svelte +39 -0
- package/dist/components/ui/select/select-content.svelte.d.ts +7 -0
- package/dist/components/ui/select/select-group-heading.svelte +16 -0
- package/dist/components/ui/select/select-group-heading.svelte.d.ts +4 -0
- package/dist/components/ui/select/select-item.svelte +37 -0
- package/dist/components/ui/select/select-item.svelte.d.ts +4 -0
- package/dist/components/ui/select/select-scroll-down-button.svelte +19 -0
- package/dist/components/ui/select/select-scroll-down-button.svelte.d.ts +4 -0
- package/dist/components/ui/select/select-scroll-up-button.svelte +19 -0
- package/dist/components/ui/select/select-scroll-up-button.svelte.d.ts +4 -0
- package/dist/components/ui/select/select-separator.svelte +13 -0
- package/dist/components/ui/select/select-separator.svelte.d.ts +4 -0
- package/dist/components/ui/select/select-trigger.svelte +24 -0
- package/dist/components/ui/select/select-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/separator/index.d.ts +2 -0
- package/dist/components/ui/separator/index.js +4 -0
- package/dist/components/ui/separator/separator.svelte +22 -0
- package/dist/components/ui/separator/separator.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/index.d.ts +12 -0
- package/dist/components/ui/sheet/index.js +14 -0
- package/dist/components/ui/sheet/sheet-content.svelte +53 -0
- package/dist/components/ui/sheet/sheet-content.svelte.d.ts +62 -0
- package/dist/components/ui/sheet/sheet-description.svelte +16 -0
- package/dist/components/ui/sheet/sheet-description.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet-footer.svelte +20 -0
- package/dist/components/ui/sheet/sheet-footer.svelte.d.ts +5 -0
- package/dist/components/ui/sheet/sheet-header.svelte +20 -0
- package/dist/components/ui/sheet/sheet-header.svelte.d.ts +5 -0
- package/dist/components/ui/sheet/sheet-overlay.svelte +21 -0
- package/dist/components/ui/sheet/sheet-overlay.svelte.d.ts +6 -0
- package/dist/components/ui/sheet/sheet-title.svelte +16 -0
- package/dist/components/ui/sheet/sheet-title.svelte.d.ts +4 -0
- package/dist/components/ui/skeleton/index.d.ts +2 -0
- package/dist/components/ui/skeleton/index.js +4 -0
- package/dist/components/ui/skeleton/skeleton.svelte +17 -0
- package/dist/components/ui/skeleton/skeleton.svelte.d.ts +5 -0
- package/dist/components/ui/table/index.d.ts +9 -0
- package/dist/components/ui/table/index.js +11 -0
- package/dist/components/ui/table/table-body.svelte +16 -0
- package/dist/components/ui/table/table-body.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-caption.svelte +16 -0
- package/dist/components/ui/table/table-caption.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-cell.svelte +20 -0
- package/dist/components/ui/table/table-cell.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-footer.svelte +16 -0
- package/dist/components/ui/table/table-footer.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-head.svelte +23 -0
- package/dist/components/ui/table/table-head.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-header.svelte +16 -0
- package/dist/components/ui/table/table-header.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-row.svelte +23 -0
- package/dist/components/ui/table/table-row.svelte.d.ts +5 -0
- package/dist/components/ui/table/table.svelte +18 -0
- package/dist/components/ui/table/table.svelte.d.ts +5 -0
- package/dist/components/ui/tabs/index.d.ts +6 -0
- package/dist/components/ui/tabs/index.js +8 -0
- package/dist/components/ui/tabs/tabs-content.svelte +19 -0
- package/dist/components/ui/tabs/tabs-content.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs-list.svelte +19 -0
- package/dist/components/ui/tabs/tabs-list.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte +19 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/textarea/index.d.ts +2 -0
- package/dist/components/ui/textarea/index.js +4 -0
- package/dist/components/ui/textarea/textarea.svelte +24 -0
- package/dist/components/ui/textarea/textarea.svelte.d.ts +6 -0
- package/dist/components/ui/toast.d.ts +86 -0
- package/dist/components/ui/toast.js +99 -0
- package/dist/db/schema.sql +238 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +20 -0
- package/dist/payments/index.d.ts +33 -0
- package/dist/payments/index.js +47 -0
- package/dist/payments/shop.d.ts +165 -0
- package/dist/payments/shop.js +588 -0
- package/dist/payments/stripe/client.d.ts +231 -0
- package/dist/payments/stripe/client.js +198 -0
- package/dist/payments/stripe/index.d.ts +18 -0
- package/dist/payments/stripe/index.js +17 -0
- package/dist/payments/stripe/provider.d.ts +50 -0
- package/dist/payments/stripe/provider.js +530 -0
- package/dist/payments/types.d.ts +355 -0
- package/dist/payments/types.js +7 -0
- package/dist/server/logger.d.ts +53 -0
- package/dist/server/logger.js +252 -0
- package/dist/styles/content.css +514 -0
- package/dist/styles/tokens.css +175 -0
- package/dist/utils/api.d.ts +20 -0
- package/dist/utils/api.js +109 -0
- package/dist/utils/cn.d.ts +15 -0
- package/dist/utils/cn.js +18 -0
- package/dist/utils/csrf.d.ts +22 -0
- package/dist/utils/csrf.js +72 -0
- package/dist/utils/debounce.d.ts +7 -0
- package/dist/utils/debounce.js +14 -0
- package/dist/utils/gallery.d.ts +66 -0
- package/dist/utils/gallery.js +181 -0
- package/dist/utils/gutter.d.ts +54 -0
- package/dist/utils/gutter.js +169 -0
- package/dist/utils/imageProcessor.d.ts +58 -0
- package/dist/utils/imageProcessor.js +205 -0
- package/dist/utils/json.d.ts +17 -0
- package/dist/utils/json.js +26 -0
- package/dist/utils/markdown.d.ts +101 -0
- package/dist/utils/markdown.js +947 -0
- package/dist/utils/sanitize.d.ts +25 -0
- package/dist/utils/sanitize.js +127 -0
- package/dist/utils/validation.d.ts +46 -0
- package/dist/utils/validation.js +169 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +5 -0
- package/package.json +129 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitize HTML content to prevent XSS attacks
|
|
3
|
+
* @param {string} html - Raw HTML string to sanitize
|
|
4
|
+
* @returns {string} - Sanitized HTML safe for rendering
|
|
5
|
+
*/
|
|
6
|
+
export function sanitizeHTML(html: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Sanitize SVG content specifically (stricter rules for SVG)
|
|
9
|
+
* @param {string} svg - Raw SVG string to sanitize
|
|
10
|
+
* @returns {string} - Sanitized SVG safe for rendering
|
|
11
|
+
*/
|
|
12
|
+
export function sanitizeSVG(svg: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Sanitize markdown-generated HTML with appropriate security rules
|
|
15
|
+
* This is a convenience wrapper for sanitizeHTML with markdown-specific settings
|
|
16
|
+
* @param {string} markdownHTML - HTML generated from markdown parsing
|
|
17
|
+
* @returns {string} - Sanitized HTML safe for rendering
|
|
18
|
+
*/
|
|
19
|
+
export function sanitizeMarkdown(markdownHTML: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Sanitize URL to prevent dangerous protocols
|
|
22
|
+
* @param {string} url - URL to sanitize
|
|
23
|
+
* @returns {string} - Sanitized URL (returns empty string if dangerous)
|
|
24
|
+
*/
|
|
25
|
+
export function sanitizeURL(url: string): string;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized sanitization utilities for XSS prevention
|
|
3
|
+
* Uses isomorphic-dompurify for both server-side and client-side sanitization
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import DOMPurify from 'isomorphic-dompurify';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Sanitize HTML content to prevent XSS attacks
|
|
10
|
+
* @param {string} html - Raw HTML string to sanitize
|
|
11
|
+
* @returns {string} - Sanitized HTML safe for rendering
|
|
12
|
+
*/
|
|
13
|
+
export function sanitizeHTML(html) {
|
|
14
|
+
if (!html || typeof html !== 'string') {
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const config = {
|
|
19
|
+
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'link', 'style', 'form', 'input', 'button', 'base', 'meta'],
|
|
20
|
+
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onblur', 'onchange', 'onsubmit', 'onmouseenter', 'onmouseleave'],
|
|
21
|
+
ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto|tel):)/i,
|
|
22
|
+
ALLOW_DATA_ATTR: false,
|
|
23
|
+
KEEP_CONTENT: true,
|
|
24
|
+
SAFE_FOR_TEMPLATES: true
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return DOMPurify.sanitize(html, config);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Sanitize SVG content specifically (stricter rules for SVG)
|
|
32
|
+
* @param {string} svg - Raw SVG string to sanitize
|
|
33
|
+
* @returns {string} - Sanitized SVG safe for rendering
|
|
34
|
+
*/
|
|
35
|
+
export function sanitizeSVG(svg) {
|
|
36
|
+
if (!svg || typeof svg !== 'string') {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return DOMPurify.sanitize(svg, {
|
|
41
|
+
USE_PROFILES: { svg: true, svgFilters: true },
|
|
42
|
+
ALLOWED_TAGS: [
|
|
43
|
+
'svg', 'g', 'path', 'circle', 'rect', 'line', 'polyline', 'polygon',
|
|
44
|
+
'ellipse', 'text', 'tspan', 'defs', 'marker', 'pattern', 'clipPath',
|
|
45
|
+
'mask', 'linearGradient', 'radialGradient', 'stop', 'use', 'symbol',
|
|
46
|
+
'title', 'desc'
|
|
47
|
+
],
|
|
48
|
+
ALLOWED_ATTR: [
|
|
49
|
+
'class', 'id', 'transform', 'fill', 'stroke', 'stroke-width',
|
|
50
|
+
'x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'r', 'rx', 'ry',
|
|
51
|
+
'width', 'height', 'd', 'points', 'viewBox', 'xmlns', 'version',
|
|
52
|
+
'preserveAspectRatio', 'opacity', 'fill-opacity', 'stroke-opacity'
|
|
53
|
+
],
|
|
54
|
+
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'link', 'style', 'foreignObject', 'image', 'a'],
|
|
55
|
+
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onblur', 'style', 'href', 'xlink:href'],
|
|
56
|
+
ALLOWED_URI_REGEXP: /^$/,
|
|
57
|
+
KEEP_CONTENT: false,
|
|
58
|
+
SAFE_FOR_TEMPLATES: true
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Sanitize markdown-generated HTML with appropriate security rules
|
|
64
|
+
* This is a convenience wrapper for sanitizeHTML with markdown-specific settings
|
|
65
|
+
* @param {string} markdownHTML - HTML generated from markdown parsing
|
|
66
|
+
* @returns {string} - Sanitized HTML safe for rendering
|
|
67
|
+
*/
|
|
68
|
+
export function sanitizeMarkdown(markdownHTML) {
|
|
69
|
+
if (!markdownHTML || typeof markdownHTML !== 'string') {
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// For markdown, we allow a broader set of tags but still sanitize
|
|
74
|
+
return DOMPurify.sanitize(markdownHTML, {
|
|
75
|
+
ALLOWED_TAGS: [
|
|
76
|
+
'a', 'abbr', 'b', 'blockquote', 'br', 'code', 'dd', 'del', 'div', 'dl', 'dt',
|
|
77
|
+
'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd',
|
|
78
|
+
'li', 'mark', 'ol', 'p', 'pre', 'q', 's', 'samp', 'small', 'span', 'strong',
|
|
79
|
+
'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'u', 'ul',
|
|
80
|
+
'var', 'input', 'label'
|
|
81
|
+
],
|
|
82
|
+
ALLOWED_ATTR: [
|
|
83
|
+
'href', 'src', 'alt', 'title', 'class', 'id', 'target', 'rel',
|
|
84
|
+
'width', 'height', 'align', 'type', 'checked', 'disabled'
|
|
85
|
+
],
|
|
86
|
+
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'link', 'style', 'form', 'button'],
|
|
87
|
+
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onblur', 'onchange', 'onsubmit', 'style'],
|
|
88
|
+
ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto|tel):)/i,
|
|
89
|
+
ALLOW_DATA_ATTR: false,
|
|
90
|
+
KEEP_CONTENT: true,
|
|
91
|
+
SAFE_FOR_TEMPLATES: true
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Sanitize URL to prevent dangerous protocols
|
|
97
|
+
* @param {string} url - URL to sanitize
|
|
98
|
+
* @returns {string} - Sanitized URL (returns empty string if dangerous)
|
|
99
|
+
*/
|
|
100
|
+
export function sanitizeURL(url) {
|
|
101
|
+
if (!url || typeof url !== 'string') {
|
|
102
|
+
return '';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Allow relative URLs
|
|
106
|
+
if (url.startsWith('/') || url.startsWith('./') || url.startsWith('../')) {
|
|
107
|
+
return url;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Check for dangerous protocols
|
|
111
|
+
const dangerous = /^(javascript|data|vbscript|file|about):/i;
|
|
112
|
+
if (dangerous.test(url)) {
|
|
113
|
+
return '';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Only allow safe protocols
|
|
117
|
+
const safe = /^(https?|mailto|tel):/i;
|
|
118
|
+
if (!safe.test(url)) {
|
|
119
|
+
// If no protocol, assume relative
|
|
120
|
+
if (!url.includes(':')) {
|
|
121
|
+
return url;
|
|
122
|
+
}
|
|
123
|
+
return '';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return url;
|
|
127
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates file signature (magic bytes) to prevent MIME type spoofing
|
|
3
|
+
* @param {File} file - The file to validate
|
|
4
|
+
* @param {string} expectedType - Expected MIME type
|
|
5
|
+
* @returns {Promise<boolean>} True if file signature matches expected type
|
|
6
|
+
*/
|
|
7
|
+
export function validateFileSignature(file: File, expectedType: string): Promise<boolean>;
|
|
8
|
+
/**
|
|
9
|
+
* Sanitizes objects to prevent prototype pollution attacks
|
|
10
|
+
* Recursively removes dangerous keys from objects
|
|
11
|
+
* @param {*} obj - Object to sanitize
|
|
12
|
+
* @returns {*} Sanitized object
|
|
13
|
+
*/
|
|
14
|
+
export function sanitizeObject(obj: any): any;
|
|
15
|
+
/**
|
|
16
|
+
* Sanitizes filename to prevent injection attacks
|
|
17
|
+
* Removes dangerous characters and keywords
|
|
18
|
+
* @param {string} filename - Filename to sanitize
|
|
19
|
+
* @returns {string} Sanitized filename
|
|
20
|
+
*/
|
|
21
|
+
export function sanitizeFilename(filename: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Enhanced path traversal prevention
|
|
24
|
+
* Validates that paths don't contain directory traversal attempts
|
|
25
|
+
* @param {string} path - Path to validate
|
|
26
|
+
* @returns {boolean} True if path is safe
|
|
27
|
+
*/
|
|
28
|
+
export function validatePath(path: string): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Email validation (strengthened)
|
|
31
|
+
* @param {string} email - Email address to validate
|
|
32
|
+
* @returns {boolean} True if email is valid
|
|
33
|
+
*/
|
|
34
|
+
export function validateEmail(email: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* URL validation - only allows http/https protocols
|
|
37
|
+
* @param {string} url - URL to validate
|
|
38
|
+
* @returns {boolean} True if URL is valid
|
|
39
|
+
*/
|
|
40
|
+
export function validateURL(url: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Slug validation - ensures URL-safe slugs
|
|
43
|
+
* @param {string} slug - Slug to validate
|
|
44
|
+
* @returns {boolean} True if slug is valid
|
|
45
|
+
*/
|
|
46
|
+
export function validateSlug(slug: string): boolean;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security validation utilities for file uploads and input sanitization
|
|
3
|
+
* @module lib/utils/validation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// File signature database for magic byte validation
|
|
7
|
+
const FILE_SIGNATURES = {
|
|
8
|
+
'image/jpeg': [
|
|
9
|
+
[0xFF, 0xD8, 0xFF, 0xE0], // JPEG/JFIF
|
|
10
|
+
[0xFF, 0xD8, 0xFF, 0xE1], // JPEG/Exif
|
|
11
|
+
[0xFF, 0xD8, 0xFF, 0xE8] // JPEG/SPIFF
|
|
12
|
+
],
|
|
13
|
+
'image/png': [[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]],
|
|
14
|
+
'image/gif': [
|
|
15
|
+
[0x47, 0x49, 0x46, 0x38, 0x37, 0x61], // GIF87a
|
|
16
|
+
[0x47, 0x49, 0x46, 0x38, 0x39, 0x61] // GIF89a
|
|
17
|
+
],
|
|
18
|
+
'image/webp': [[0x52, 0x49, 0x46, 0x46]] // RIFF (WebP container)
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validates file signature (magic bytes) to prevent MIME type spoofing
|
|
23
|
+
* @param {File} file - The file to validate
|
|
24
|
+
* @param {string} expectedType - Expected MIME type
|
|
25
|
+
* @returns {Promise<boolean>} True if file signature matches expected type
|
|
26
|
+
*/
|
|
27
|
+
export async function validateFileSignature(file, expectedType) {
|
|
28
|
+
const buffer = new Uint8Array(await file.arrayBuffer());
|
|
29
|
+
const signatures = FILE_SIGNATURES[expectedType];
|
|
30
|
+
|
|
31
|
+
if (!signatures) return false;
|
|
32
|
+
|
|
33
|
+
return signatures.some(sig =>
|
|
34
|
+
sig.every((byte, i) => buffer[i] === byte)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Dangerous object keys that can lead to prototype pollution
|
|
39
|
+
const DANGEROUS_KEYS = ['__proto__', 'constructor', 'prototype'];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Sanitizes objects to prevent prototype pollution attacks
|
|
43
|
+
* Recursively removes dangerous keys from objects
|
|
44
|
+
* @param {*} obj - Object to sanitize
|
|
45
|
+
* @returns {*} Sanitized object
|
|
46
|
+
*/
|
|
47
|
+
export function sanitizeObject(obj) {
|
|
48
|
+
if (typeof obj !== 'object' || obj === null) return obj;
|
|
49
|
+
|
|
50
|
+
// Handle arrays specially to preserve array type
|
|
51
|
+
if (Array.isArray(obj)) {
|
|
52
|
+
return Object.freeze(obj.map(item =>
|
|
53
|
+
typeof item === 'object' && item !== null ? sanitizeObject(item) : item
|
|
54
|
+
));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const sanitized = {};
|
|
58
|
+
|
|
59
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
60
|
+
// Check for dangerous keys (case-insensitive and with brackets)
|
|
61
|
+
const lowerKey = key.toLowerCase();
|
|
62
|
+
if (DANGEROUS_KEYS.includes(key) ||
|
|
63
|
+
DANGEROUS_KEYS.includes(lowerKey) ||
|
|
64
|
+
key.includes('[') ||
|
|
65
|
+
key.includes(']')) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
sanitized[key] = typeof value === 'object' && value !== null
|
|
70
|
+
? sanitizeObject(value)
|
|
71
|
+
: value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return Object.freeze(sanitized);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Sanitizes filename to prevent injection attacks
|
|
79
|
+
* Removes dangerous characters and keywords
|
|
80
|
+
* @param {string} filename - Filename to sanitize
|
|
81
|
+
* @returns {string} Sanitized filename
|
|
82
|
+
*/
|
|
83
|
+
export function sanitizeFilename(filename) {
|
|
84
|
+
if (!filename || typeof filename !== 'string') return '';
|
|
85
|
+
|
|
86
|
+
// Remove or replace dangerous characters
|
|
87
|
+
let clean = filename
|
|
88
|
+
.replace(/[<>:"\\|?*\x00-\x1F]/g, '') // Remove special chars
|
|
89
|
+
.replace(/\.\./g, '_') // Replace .. with _
|
|
90
|
+
.replace(/script/gi, '') // Remove 'script' (case-insensitive)
|
|
91
|
+
.replace(/javascript/gi, '') // Remove 'javascript'
|
|
92
|
+
.replace(/eval/gi, '') // Remove 'eval'
|
|
93
|
+
.replace(/\s+/g, '_') // Replace spaces with underscores
|
|
94
|
+
.replace(/^\.+/, '') // Remove leading dots
|
|
95
|
+
.trim();
|
|
96
|
+
|
|
97
|
+
// Ensure filename is not empty and has reasonable length
|
|
98
|
+
if (clean.length === 0) clean = 'file';
|
|
99
|
+
if (clean.length > 255) clean = clean.substring(0, 255);
|
|
100
|
+
|
|
101
|
+
return clean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Enhanced path traversal prevention
|
|
106
|
+
* Validates that paths don't contain directory traversal attempts
|
|
107
|
+
* @param {string} path - Path to validate
|
|
108
|
+
* @returns {boolean} True if path is safe
|
|
109
|
+
*/
|
|
110
|
+
export function validatePath(path) {
|
|
111
|
+
if (!path || typeof path !== 'string') return false;
|
|
112
|
+
|
|
113
|
+
// Prevent path traversal with various encoding tricks
|
|
114
|
+
const normalized = path.toLowerCase().replace(/\\/g, '/');
|
|
115
|
+
|
|
116
|
+
// Check for directory traversal patterns
|
|
117
|
+
if (normalized.includes('..') ||
|
|
118
|
+
normalized.includes('//') ||
|
|
119
|
+
normalized.includes('%2e%2e') || // URL encoded ..
|
|
120
|
+
normalized.includes('..%2f') ||
|
|
121
|
+
normalized.includes('%2f..') ||
|
|
122
|
+
normalized.includes('0x2e0x2e') || // Hex encoded ..
|
|
123
|
+
normalized.match(/\.\.[\\/]/) ||
|
|
124
|
+
normalized.match(/[\\/]\.\./) ||
|
|
125
|
+
path.startsWith('/') || // Absolute paths
|
|
126
|
+
path.startsWith('\\')) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Only allow alphanumeric, dash, underscore, slash, dot (for extensions)
|
|
131
|
+
if (!/^[a-zA-Z0-9/_.-]+$/.test(path)) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Email validation (strengthened)
|
|
140
|
+
* @param {string} email - Email address to validate
|
|
141
|
+
* @returns {boolean} True if email is valid
|
|
142
|
+
*/
|
|
143
|
+
export function validateEmail(email) {
|
|
144
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
145
|
+
return emailRegex.test(email) && email.length < 255;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* URL validation - only allows http/https protocols
|
|
150
|
+
* @param {string} url - URL to validate
|
|
151
|
+
* @returns {boolean} True if URL is valid
|
|
152
|
+
*/
|
|
153
|
+
export function validateURL(url) {
|
|
154
|
+
try {
|
|
155
|
+
const parsed = new URL(url);
|
|
156
|
+
return ['http:', 'https:'].includes(parsed.protocol);
|
|
157
|
+
} catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Slug validation - ensures URL-safe slugs
|
|
164
|
+
* @param {string} slug - Slug to validate
|
|
165
|
+
* @returns {boolean} True if slug is valid
|
|
166
|
+
*/
|
|
167
|
+
export function validateSlug(slug) {
|
|
168
|
+
return /^[a-z0-9-]+$/.test(slug) && slug.length > 0 && slug.length < 200;
|
|
169
|
+
}
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@autumnsgrove/groveengine",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Multi-tenant blog engine for Grove Platform. Features gutter annotations, markdown editing, magic code auth, and Cloudflare Workers deployment.",
|
|
5
|
+
"author": "AutumnsGrove",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/AutumnsGrove/GroveEngine.git",
|
|
10
|
+
"directory": "packages/engine"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"blog",
|
|
14
|
+
"cms",
|
|
15
|
+
"sveltekit",
|
|
16
|
+
"cloudflare",
|
|
17
|
+
"multi-tenant"
|
|
18
|
+
],
|
|
19
|
+
"type": "module",
|
|
20
|
+
"svelte": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"svelte": "./dist/index.js",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./components/custom/*": {
|
|
29
|
+
"types": "./dist/components/custom/*.svelte.d.ts",
|
|
30
|
+
"svelte": "./dist/components/custom/*.svelte"
|
|
31
|
+
},
|
|
32
|
+
"./components/admin/*": {
|
|
33
|
+
"types": "./dist/components/admin/*.svelte.d.ts",
|
|
34
|
+
"svelte": "./dist/components/admin/*.svelte"
|
|
35
|
+
},
|
|
36
|
+
"./components/gallery/*": {
|
|
37
|
+
"types": "./dist/components/gallery/*.svelte.d.ts",
|
|
38
|
+
"svelte": "./dist/components/gallery/*.svelte"
|
|
39
|
+
},
|
|
40
|
+
"./components/ui": {
|
|
41
|
+
"types": "./dist/components/ui/index.d.ts",
|
|
42
|
+
"svelte": "./dist/components/ui/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./utils/*": {
|
|
45
|
+
"types": "./dist/utils/*.d.ts",
|
|
46
|
+
"default": "./dist/utils/*.js"
|
|
47
|
+
},
|
|
48
|
+
"./auth/*": {
|
|
49
|
+
"types": "./dist/auth/*.d.ts",
|
|
50
|
+
"default": "./dist/auth/*.js"
|
|
51
|
+
},
|
|
52
|
+
"./server/*": {
|
|
53
|
+
"types": "./dist/server/*.d.ts",
|
|
54
|
+
"default": "./dist/server/*.js"
|
|
55
|
+
},
|
|
56
|
+
"./payments": {
|
|
57
|
+
"types": "./dist/payments/index.d.ts",
|
|
58
|
+
"default": "./dist/payments/index.js"
|
|
59
|
+
},
|
|
60
|
+
"./payments/*": {
|
|
61
|
+
"types": "./dist/payments/*.d.ts",
|
|
62
|
+
"default": "./dist/payments/*.js"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"files": [
|
|
66
|
+
"dist",
|
|
67
|
+
"!dist/**/*.test.*"
|
|
68
|
+
],
|
|
69
|
+
"scripts": {
|
|
70
|
+
"dev": "vite dev",
|
|
71
|
+
"dev:wrangler": "wrangler pages dev -- vite dev",
|
|
72
|
+
"build": "vite build",
|
|
73
|
+
"build:package": "svelte-kit sync && svelte-package -o dist",
|
|
74
|
+
"package": "svelte-kit sync && svelte-package -o dist",
|
|
75
|
+
"prepublishOnly": "npm run package",
|
|
76
|
+
"preview": "vite preview",
|
|
77
|
+
"audit": "npm audit --audit-level=moderate",
|
|
78
|
+
"audit:fix": "npm audit fix",
|
|
79
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
80
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
81
|
+
"test": "vitest",
|
|
82
|
+
"test:ui": "vitest --ui",
|
|
83
|
+
"test:run": "vitest run",
|
|
84
|
+
"test:security": "vitest run tests/security",
|
|
85
|
+
"test:coverage": "vitest run --coverage",
|
|
86
|
+
"test:watch": "vitest watch"
|
|
87
|
+
},
|
|
88
|
+
"peerDependencies": {
|
|
89
|
+
"svelte": "^5.0.0",
|
|
90
|
+
"@sveltejs/kit": "^2.0.0",
|
|
91
|
+
"tailwindcss": "^3.4.0"
|
|
92
|
+
},
|
|
93
|
+
"devDependencies": {
|
|
94
|
+
"@lucide/svelte": "^0.482.0",
|
|
95
|
+
"@sveltejs/adapter-cloudflare": "^7.2.4",
|
|
96
|
+
"@sveltejs/kit": "^2.5.28",
|
|
97
|
+
"@sveltejs/package": "^2.3.0",
|
|
98
|
+
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
|
99
|
+
"@tailwindcss/typography": "^0.5.19",
|
|
100
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
101
|
+
"@testing-library/svelte": "^5.2.9",
|
|
102
|
+
"@vitest/ui": "^4.0.14",
|
|
103
|
+
"autoprefixer": "^10.4.22",
|
|
104
|
+
"bits-ui": "^1.8.0",
|
|
105
|
+
"happy-dom": "^20.0.11",
|
|
106
|
+
"jsdom": "^27.2.0",
|
|
107
|
+
"postcss": "^8.5.6",
|
|
108
|
+
"svelte": "^5.1.9",
|
|
109
|
+
"svelte-check": "^4.0.0",
|
|
110
|
+
"tailwind-variants": "^0.2.1",
|
|
111
|
+
"tailwindcss": "^3.4.18",
|
|
112
|
+
"typescript": "^5.6.0",
|
|
113
|
+
"vite": "^5.4.10",
|
|
114
|
+
"vitest": "^4.0.14"
|
|
115
|
+
},
|
|
116
|
+
"dependencies": {
|
|
117
|
+
"@types/dompurify": "^3.0.5",
|
|
118
|
+
"chart.js": "^4.5.1",
|
|
119
|
+
"clsx": "^2.1.1",
|
|
120
|
+
"dompurify": "^3.3.0",
|
|
121
|
+
"gray-matter": "^4.0.3",
|
|
122
|
+
"isomorphic-dompurify": "^2.33.0",
|
|
123
|
+
"lucide-svelte": "^0.554.0",
|
|
124
|
+
"marked": "^17.0.1",
|
|
125
|
+
"mermaid": "^11.12.1",
|
|
126
|
+
"sonner": "^2.0.7",
|
|
127
|
+
"tailwind-merge": "^3.4.0"
|
|
128
|
+
}
|
|
129
|
+
}
|