@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.
Files changed (219) hide show
  1. package/README.md +163 -0
  2. package/dist/auth/jwt.d.ts +14 -0
  3. package/dist/auth/jwt.js +109 -0
  4. package/dist/auth/session.d.ts +42 -0
  5. package/dist/auth/session.js +105 -0
  6. package/dist/components/admin/GutterManager.svelte +910 -0
  7. package/dist/components/admin/GutterManager.svelte.d.ts +15 -0
  8. package/dist/components/admin/MarkdownEditor.svelte +3114 -0
  9. package/dist/components/admin/MarkdownEditor.svelte.d.ts +43 -0
  10. package/dist/components/custom/CollapsibleSection.svelte +74 -0
  11. package/dist/components/custom/CollapsibleSection.svelte.d.ts +15 -0
  12. package/dist/components/custom/ContentWithGutter.svelte +646 -0
  13. package/dist/components/custom/ContentWithGutter.svelte.d.ts +19 -0
  14. package/dist/components/custom/GutterItem.svelte +201 -0
  15. package/dist/components/custom/GutterItem.svelte.d.ts +11 -0
  16. package/dist/components/custom/LeftGutter.svelte +271 -0
  17. package/dist/components/custom/LeftGutter.svelte.d.ts +17 -0
  18. package/dist/components/custom/MobileTOC.svelte +273 -0
  19. package/dist/components/custom/MobileTOC.svelte.d.ts +11 -0
  20. package/dist/components/custom/TableOfContents.svelte +163 -0
  21. package/dist/components/custom/TableOfContents.svelte.d.ts +11 -0
  22. package/dist/components/gallery/ImageGallery.svelte +681 -0
  23. package/dist/components/gallery/ImageGallery.svelte.d.ts +11 -0
  24. package/dist/components/gallery/Lightbox.svelte +107 -0
  25. package/dist/components/gallery/Lightbox.svelte.d.ts +19 -0
  26. package/dist/components/gallery/LightboxCaption.svelte +25 -0
  27. package/dist/components/gallery/LightboxCaption.svelte.d.ts +11 -0
  28. package/dist/components/gallery/ZoomableImage.svelte +163 -0
  29. package/dist/components/gallery/ZoomableImage.svelte.d.ts +17 -0
  30. package/dist/components/ui/Accordion.svelte +74 -0
  31. package/dist/components/ui/Accordion.svelte.d.ts +42 -0
  32. package/dist/components/ui/Badge.svelte +48 -0
  33. package/dist/components/ui/Badge.svelte.d.ts +26 -0
  34. package/dist/components/ui/Button.svelte +74 -0
  35. package/dist/components/ui/Button.svelte.d.ts +34 -0
  36. package/dist/components/ui/Card.svelte +102 -0
  37. package/dist/components/ui/Card.svelte.d.ts +46 -0
  38. package/dist/components/ui/Dialog.svelte +91 -0
  39. package/dist/components/ui/Dialog.svelte.d.ts +43 -0
  40. package/dist/components/ui/Input.svelte +81 -0
  41. package/dist/components/ui/Input.svelte.d.ts +35 -0
  42. package/dist/components/ui/Select.svelte +69 -0
  43. package/dist/components/ui/Select.svelte.d.ts +36 -0
  44. package/dist/components/ui/Sheet.svelte +98 -0
  45. package/dist/components/ui/Sheet.svelte.d.ts +45 -0
  46. package/dist/components/ui/Skeleton.svelte +31 -0
  47. package/dist/components/ui/Skeleton.svelte.d.ts +26 -0
  48. package/dist/components/ui/Table.svelte +59 -0
  49. package/dist/components/ui/Table.svelte.d.ts +44 -0
  50. package/dist/components/ui/Tabs.svelte +76 -0
  51. package/dist/components/ui/Tabs.svelte.d.ts +41 -0
  52. package/dist/components/ui/Textarea.svelte +81 -0
  53. package/dist/components/ui/Textarea.svelte.d.ts +35 -0
  54. package/dist/components/ui/Toast.svelte +18 -0
  55. package/dist/components/ui/Toast.svelte.d.ts +7 -0
  56. package/dist/components/ui/accordion/accordion-content.svelte +24 -0
  57. package/dist/components/ui/accordion/accordion-content.svelte.d.ts +4 -0
  58. package/dist/components/ui/accordion/accordion-item.svelte +12 -0
  59. package/dist/components/ui/accordion/accordion-item.svelte.d.ts +4 -0
  60. package/dist/components/ui/accordion/accordion-trigger.svelte +29 -0
  61. package/dist/components/ui/accordion/accordion-trigger.svelte.d.ts +7 -0
  62. package/dist/components/ui/accordion/index.d.ts +6 -0
  63. package/dist/components/ui/accordion/index.js +8 -0
  64. package/dist/components/ui/badge/badge.svelte +50 -0
  65. package/dist/components/ui/badge/badge.svelte.d.ts +60 -0
  66. package/dist/components/ui/badge/index.d.ts +2 -0
  67. package/dist/components/ui/badge/index.js +2 -0
  68. package/dist/components/ui/button/button.svelte +82 -0
  69. package/dist/components/ui/button/button.svelte.d.ts +132 -0
  70. package/dist/components/ui/button/index.d.ts +2 -0
  71. package/dist/components/ui/button/index.js +4 -0
  72. package/dist/components/ui/card/card-content.svelte +16 -0
  73. package/dist/components/ui/card/card-content.svelte.d.ts +5 -0
  74. package/dist/components/ui/card/card-description.svelte +16 -0
  75. package/dist/components/ui/card/card-description.svelte.d.ts +5 -0
  76. package/dist/components/ui/card/card-footer.svelte +16 -0
  77. package/dist/components/ui/card/card-footer.svelte.d.ts +5 -0
  78. package/dist/components/ui/card/card-header.svelte +16 -0
  79. package/dist/components/ui/card/card-header.svelte.d.ts +5 -0
  80. package/dist/components/ui/card/card-title.svelte +25 -0
  81. package/dist/components/ui/card/card-title.svelte.d.ts +8 -0
  82. package/dist/components/ui/card/card.svelte +20 -0
  83. package/dist/components/ui/card/card.svelte.d.ts +5 -0
  84. package/dist/components/ui/card/index.d.ts +7 -0
  85. package/dist/components/ui/card/index.js +9 -0
  86. package/dist/components/ui/dialog/dialog-content.svelte +38 -0
  87. package/dist/components/ui/dialog/dialog-content.svelte.d.ts +9 -0
  88. package/dist/components/ui/dialog/dialog-description.svelte +16 -0
  89. package/dist/components/ui/dialog/dialog-description.svelte.d.ts +4 -0
  90. package/dist/components/ui/dialog/dialog-footer.svelte +20 -0
  91. package/dist/components/ui/dialog/dialog-footer.svelte.d.ts +5 -0
  92. package/dist/components/ui/dialog/dialog-header.svelte +20 -0
  93. package/dist/components/ui/dialog/dialog-header.svelte.d.ts +5 -0
  94. package/dist/components/ui/dialog/dialog-overlay.svelte +19 -0
  95. package/dist/components/ui/dialog/dialog-overlay.svelte.d.ts +4 -0
  96. package/dist/components/ui/dialog/dialog-title.svelte +16 -0
  97. package/dist/components/ui/dialog/dialog-title.svelte.d.ts +4 -0
  98. package/dist/components/ui/dialog/index.d.ts +12 -0
  99. package/dist/components/ui/dialog/index.js +14 -0
  100. package/dist/components/ui/index.d.ts +26 -0
  101. package/dist/components/ui/index.js +29 -0
  102. package/dist/components/ui/input/index.d.ts +2 -0
  103. package/dist/components/ui/input/index.js +4 -0
  104. package/dist/components/ui/input/input.svelte +46 -0
  105. package/dist/components/ui/input/input.svelte.d.ts +13 -0
  106. package/dist/components/ui/select/index.d.ts +11 -0
  107. package/dist/components/ui/select/index.js +13 -0
  108. package/dist/components/ui/select/select-content.svelte +39 -0
  109. package/dist/components/ui/select/select-content.svelte.d.ts +7 -0
  110. package/dist/components/ui/select/select-group-heading.svelte +16 -0
  111. package/dist/components/ui/select/select-group-heading.svelte.d.ts +4 -0
  112. package/dist/components/ui/select/select-item.svelte +37 -0
  113. package/dist/components/ui/select/select-item.svelte.d.ts +4 -0
  114. package/dist/components/ui/select/select-scroll-down-button.svelte +19 -0
  115. package/dist/components/ui/select/select-scroll-down-button.svelte.d.ts +4 -0
  116. package/dist/components/ui/select/select-scroll-up-button.svelte +19 -0
  117. package/dist/components/ui/select/select-scroll-up-button.svelte.d.ts +4 -0
  118. package/dist/components/ui/select/select-separator.svelte +13 -0
  119. package/dist/components/ui/select/select-separator.svelte.d.ts +4 -0
  120. package/dist/components/ui/select/select-trigger.svelte +24 -0
  121. package/dist/components/ui/select/select-trigger.svelte.d.ts +4 -0
  122. package/dist/components/ui/separator/index.d.ts +2 -0
  123. package/dist/components/ui/separator/index.js +4 -0
  124. package/dist/components/ui/separator/separator.svelte +22 -0
  125. package/dist/components/ui/separator/separator.svelte.d.ts +4 -0
  126. package/dist/components/ui/sheet/index.d.ts +12 -0
  127. package/dist/components/ui/sheet/index.js +14 -0
  128. package/dist/components/ui/sheet/sheet-content.svelte +53 -0
  129. package/dist/components/ui/sheet/sheet-content.svelte.d.ts +62 -0
  130. package/dist/components/ui/sheet/sheet-description.svelte +16 -0
  131. package/dist/components/ui/sheet/sheet-description.svelte.d.ts +4 -0
  132. package/dist/components/ui/sheet/sheet-footer.svelte +20 -0
  133. package/dist/components/ui/sheet/sheet-footer.svelte.d.ts +5 -0
  134. package/dist/components/ui/sheet/sheet-header.svelte +20 -0
  135. package/dist/components/ui/sheet/sheet-header.svelte.d.ts +5 -0
  136. package/dist/components/ui/sheet/sheet-overlay.svelte +21 -0
  137. package/dist/components/ui/sheet/sheet-overlay.svelte.d.ts +6 -0
  138. package/dist/components/ui/sheet/sheet-title.svelte +16 -0
  139. package/dist/components/ui/sheet/sheet-title.svelte.d.ts +4 -0
  140. package/dist/components/ui/skeleton/index.d.ts +2 -0
  141. package/dist/components/ui/skeleton/index.js +4 -0
  142. package/dist/components/ui/skeleton/skeleton.svelte +17 -0
  143. package/dist/components/ui/skeleton/skeleton.svelte.d.ts +5 -0
  144. package/dist/components/ui/table/index.d.ts +9 -0
  145. package/dist/components/ui/table/index.js +11 -0
  146. package/dist/components/ui/table/table-body.svelte +16 -0
  147. package/dist/components/ui/table/table-body.svelte.d.ts +5 -0
  148. package/dist/components/ui/table/table-caption.svelte +16 -0
  149. package/dist/components/ui/table/table-caption.svelte.d.ts +5 -0
  150. package/dist/components/ui/table/table-cell.svelte +20 -0
  151. package/dist/components/ui/table/table-cell.svelte.d.ts +5 -0
  152. package/dist/components/ui/table/table-footer.svelte +16 -0
  153. package/dist/components/ui/table/table-footer.svelte.d.ts +5 -0
  154. package/dist/components/ui/table/table-head.svelte +23 -0
  155. package/dist/components/ui/table/table-head.svelte.d.ts +5 -0
  156. package/dist/components/ui/table/table-header.svelte +16 -0
  157. package/dist/components/ui/table/table-header.svelte.d.ts +5 -0
  158. package/dist/components/ui/table/table-row.svelte +23 -0
  159. package/dist/components/ui/table/table-row.svelte.d.ts +5 -0
  160. package/dist/components/ui/table/table.svelte +18 -0
  161. package/dist/components/ui/table/table.svelte.d.ts +5 -0
  162. package/dist/components/ui/tabs/index.d.ts +6 -0
  163. package/dist/components/ui/tabs/index.js +8 -0
  164. package/dist/components/ui/tabs/tabs-content.svelte +19 -0
  165. package/dist/components/ui/tabs/tabs-content.svelte.d.ts +4 -0
  166. package/dist/components/ui/tabs/tabs-list.svelte +19 -0
  167. package/dist/components/ui/tabs/tabs-list.svelte.d.ts +4 -0
  168. package/dist/components/ui/tabs/tabs-trigger.svelte +19 -0
  169. package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
  170. package/dist/components/ui/textarea/index.d.ts +2 -0
  171. package/dist/components/ui/textarea/index.js +4 -0
  172. package/dist/components/ui/textarea/textarea.svelte +24 -0
  173. package/dist/components/ui/textarea/textarea.svelte.d.ts +6 -0
  174. package/dist/components/ui/toast.d.ts +86 -0
  175. package/dist/components/ui/toast.js +99 -0
  176. package/dist/db/schema.sql +238 -0
  177. package/dist/index.d.ts +14 -0
  178. package/dist/index.js +20 -0
  179. package/dist/payments/index.d.ts +33 -0
  180. package/dist/payments/index.js +47 -0
  181. package/dist/payments/shop.d.ts +165 -0
  182. package/dist/payments/shop.js +588 -0
  183. package/dist/payments/stripe/client.d.ts +231 -0
  184. package/dist/payments/stripe/client.js +198 -0
  185. package/dist/payments/stripe/index.d.ts +18 -0
  186. package/dist/payments/stripe/index.js +17 -0
  187. package/dist/payments/stripe/provider.d.ts +50 -0
  188. package/dist/payments/stripe/provider.js +530 -0
  189. package/dist/payments/types.d.ts +355 -0
  190. package/dist/payments/types.js +7 -0
  191. package/dist/server/logger.d.ts +53 -0
  192. package/dist/server/logger.js +252 -0
  193. package/dist/styles/content.css +514 -0
  194. package/dist/styles/tokens.css +175 -0
  195. package/dist/utils/api.d.ts +20 -0
  196. package/dist/utils/api.js +109 -0
  197. package/dist/utils/cn.d.ts +15 -0
  198. package/dist/utils/cn.js +18 -0
  199. package/dist/utils/csrf.d.ts +22 -0
  200. package/dist/utils/csrf.js +72 -0
  201. package/dist/utils/debounce.d.ts +7 -0
  202. package/dist/utils/debounce.js +14 -0
  203. package/dist/utils/gallery.d.ts +66 -0
  204. package/dist/utils/gallery.js +181 -0
  205. package/dist/utils/gutter.d.ts +54 -0
  206. package/dist/utils/gutter.js +169 -0
  207. package/dist/utils/imageProcessor.d.ts +58 -0
  208. package/dist/utils/imageProcessor.js +205 -0
  209. package/dist/utils/json.d.ts +17 -0
  210. package/dist/utils/json.js +26 -0
  211. package/dist/utils/markdown.d.ts +101 -0
  212. package/dist/utils/markdown.js +947 -0
  213. package/dist/utils/sanitize.d.ts +25 -0
  214. package/dist/utils/sanitize.js +127 -0
  215. package/dist/utils/validation.d.ts +46 -0
  216. package/dist/utils/validation.js +169 -0
  217. package/dist/utils.d.ts +5 -0
  218. package/dist/utils.js +5 -0
  219. 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
+ }
@@ -0,0 +1,5 @@
1
+ import { type ClassValue } from "clsx";
2
+ export declare function cn(...inputs: ClassValue[]): string;
3
+ export type WithElementRef<T> = T & {
4
+ ref?: any;
5
+ };
package/dist/utils.js ADDED
@@ -0,0 +1,5 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ export function cn(...inputs) {
4
+ return twMerge(clsx(inputs));
5
+ }
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
+ }