@mdguggenbichler/slugbase-core 0.0.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.
Files changed (381) hide show
  1. package/backend/dist/app-factory.d.ts +17 -0
  2. package/backend/dist/app-factory.d.ts.map +1 -0
  3. package/backend/dist/app-factory.js +106 -0
  4. package/backend/dist/app-factory.js.map +1 -0
  5. package/backend/dist/auth/authorization.d.ts +25 -0
  6. package/backend/dist/auth/authorization.d.ts.map +1 -0
  7. package/backend/dist/auth/authorization.js +100 -0
  8. package/backend/dist/auth/authorization.js.map +1 -0
  9. package/backend/dist/auth/jwt.d.ts +5 -0
  10. package/backend/dist/auth/jwt.d.ts.map +1 -0
  11. package/backend/dist/auth/jwt.js +34 -0
  12. package/backend/dist/auth/jwt.js.map +1 -0
  13. package/backend/dist/auth/oidc.d.ts +4 -0
  14. package/backend/dist/auth/oidc.d.ts.map +1 -0
  15. package/backend/dist/auth/oidc.js +201 -0
  16. package/backend/dist/auth/oidc.js.map +1 -0
  17. package/backend/dist/config/cloud-providers.d.ts +18 -0
  18. package/backend/dist/config/cloud-providers.d.ts.map +1 -0
  19. package/backend/dist/config/cloud-providers.js +60 -0
  20. package/backend/dist/config/cloud-providers.js.map +1 -0
  21. package/backend/dist/config/cookies.d.ts +17 -0
  22. package/backend/dist/config/cookies.d.ts.map +1 -0
  23. package/backend/dist/config/cookies.js +26 -0
  24. package/backend/dist/config/cookies.js.map +1 -0
  25. package/backend/dist/config/mode.d.ts +7 -0
  26. package/backend/dist/config/mode.d.ts.map +1 -0
  27. package/backend/dist/config/mode.js +7 -0
  28. package/backend/dist/config/mode.js.map +1 -0
  29. package/backend/dist/config/swagger.d.ts +2 -0
  30. package/backend/dist/config/swagger.d.ts.map +1 -0
  31. package/backend/dist/config/swagger.js +76 -0
  32. package/backend/dist/config/swagger.js.map +1 -0
  33. package/backend/dist/config.d.ts +29 -0
  34. package/backend/dist/config.d.ts.map +1 -0
  35. package/backend/dist/config.js +41 -0
  36. package/backend/dist/config.js.map +1 -0
  37. package/backend/dist/db/index.d.ts +14 -0
  38. package/backend/dist/db/index.d.ts.map +1 -0
  39. package/backend/dist/db/index.js +114 -0
  40. package/backend/dist/db/index.js.map +1 -0
  41. package/backend/dist/db/migrate-slug-nullable.d.ts +10 -0
  42. package/backend/dist/db/migrate-slug-nullable.d.ts.map +1 -0
  43. package/backend/dist/db/migrate-slug-nullable.js +87 -0
  44. package/backend/dist/db/migrate-slug-nullable.js.map +1 -0
  45. package/backend/dist/db/migrations/001_migrate_slug_nullable.d.ts +10 -0
  46. package/backend/dist/db/migrations/001_migrate_slug_nullable.d.ts.map +1 -0
  47. package/backend/dist/db/migrations/001_migrate_slug_nullable.js +103 -0
  48. package/backend/dist/db/migrations/001_migrate_slug_nullable.js.map +1 -0
  49. package/backend/dist/db/migrations/002_add_oidc_custom_endpoints.d.ts +11 -0
  50. package/backend/dist/db/migrations/002_add_oidc_custom_endpoints.d.ts.map +1 -0
  51. package/backend/dist/db/migrations/002_add_oidc_custom_endpoints.js +92 -0
  52. package/backend/dist/db/migrations/002_add_oidc_custom_endpoints.js.map +1 -0
  53. package/backend/dist/db/migrations/003_add_bookmark_features.d.ts +5 -0
  54. package/backend/dist/db/migrations/003_add_bookmark_features.d.ts.map +1 -0
  55. package/backend/dist/db/migrations/003_add_bookmark_features.js +98 -0
  56. package/backend/dist/db/migrations/003_add_bookmark_features.js.map +1 -0
  57. package/backend/dist/db/migrations/004_make_slug_globally_unique.d.ts +12 -0
  58. package/backend/dist/db/migrations/004_make_slug_globally_unique.d.ts.map +1 -0
  59. package/backend/dist/db/migrations/004_make_slug_globally_unique.js +152 -0
  60. package/backend/dist/db/migrations/004_make_slug_globally_unique.js.map +1 -0
  61. package/backend/dist/db/migrations/005_add_email_verification.d.ts +5 -0
  62. package/backend/dist/db/migrations/005_add_email_verification.d.ts.map +1 -0
  63. package/backend/dist/db/migrations/005_add_email_verification.js +102 -0
  64. package/backend/dist/db/migrations/005_add_email_verification.js.map +1 -0
  65. package/backend/dist/db/migrations/006_refresh_tokens.d.ts +9 -0
  66. package/backend/dist/db/migrations/006_refresh_tokens.d.ts.map +1 -0
  67. package/backend/dist/db/migrations/006_refresh_tokens.js +61 -0
  68. package/backend/dist/db/migrations/006_refresh_tokens.js.map +1 -0
  69. package/backend/dist/db/migrations/007_password_reset_token_hash.d.ts +10 -0
  70. package/backend/dist/db/migrations/007_password_reset_token_hash.d.ts.map +1 -0
  71. package/backend/dist/db/migrations/007_password_reset_token_hash.js +40 -0
  72. package/backend/dist/db/migrations/007_password_reset_token_hash.js.map +1 -0
  73. package/backend/dist/db/migrations/008_slug_preferences.d.ts +8 -0
  74. package/backend/dist/db/migrations/008_slug_preferences.d.ts.map +1 -0
  75. package/backend/dist/db/migrations/008_slug_preferences.js +24 -0
  76. package/backend/dist/db/migrations/008_slug_preferences.js.map +1 -0
  77. package/backend/dist/db/migrations/009_signup_email_verified.d.ts +5 -0
  78. package/backend/dist/db/migrations/009_signup_email_verified.d.ts.map +1 -0
  79. package/backend/dist/db/migrations/009_signup_email_verified.js +79 -0
  80. package/backend/dist/db/migrations/009_signup_email_verified.js.map +1 -0
  81. package/backend/dist/db/migrations/010_organizations.d.ts +5 -0
  82. package/backend/dist/db/migrations/010_organizations.d.ts.map +1 -0
  83. package/backend/dist/db/migrations/010_organizations.js +113 -0
  84. package/backend/dist/db/migrations/010_organizations.js.map +1 -0
  85. package/backend/dist/db/migrations/011_org_scoped_teams.d.ts +11 -0
  86. package/backend/dist/db/migrations/011_org_scoped_teams.d.ts.map +1 -0
  87. package/backend/dist/db/migrations/011_org_scoped_teams.js +59 -0
  88. package/backend/dist/db/migrations/011_org_scoped_teams.js.map +1 -0
  89. package/backend/dist/db/migrations/012_org_invitations_token_hash.d.ts +10 -0
  90. package/backend/dist/db/migrations/012_org_invitations_token_hash.d.ts.map +1 -0
  91. package/backend/dist/db/migrations/012_org_invitations_token_hash.js +40 -0
  92. package/backend/dist/db/migrations/012_org_invitations_token_hash.js.map +1 -0
  93. package/backend/dist/db/migrations/013_signup_verification_token_hash.d.ts +10 -0
  94. package/backend/dist/db/migrations/013_signup_verification_token_hash.d.ts.map +1 -0
  95. package/backend/dist/db/migrations/013_signup_verification_token_hash.js +39 -0
  96. package/backend/dist/db/migrations/013_signup_verification_token_hash.js.map +1 -0
  97. package/backend/dist/db/migrations/014_stats_indexes.d.ts +8 -0
  98. package/backend/dist/db/migrations/014_stats_indexes.d.ts.map +1 -0
  99. package/backend/dist/db/migrations/014_stats_indexes.js +19 -0
  100. package/backend/dist/db/migrations/014_stats_indexes.js.map +1 -0
  101. package/backend/dist/db/migrations/015_api_tokens.d.ts +9 -0
  102. package/backend/dist/db/migrations/015_api_tokens.d.ts.map +1 -0
  103. package/backend/dist/db/migrations/015_api_tokens.js +48 -0
  104. package/backend/dist/db/migrations/015_api_tokens.js.map +1 -0
  105. package/backend/dist/db/migrations/016_free_plan_grace_ends_at.d.ts +9 -0
  106. package/backend/dist/db/migrations/016_free_plan_grace_ends_at.d.ts.map +1 -0
  107. package/backend/dist/db/migrations/016_free_plan_grace_ends_at.js +50 -0
  108. package/backend/dist/db/migrations/016_free_plan_grace_ends_at.js.map +1 -0
  109. package/backend/dist/db/migrations/017_ai_suggestions.d.ts +11 -0
  110. package/backend/dist/db/migrations/017_ai_suggestions.d.ts.map +1 -0
  111. package/backend/dist/db/migrations/017_ai_suggestions.js +78 -0
  112. package/backend/dist/db/migrations/017_ai_suggestions.js.map +1 -0
  113. package/backend/dist/db/migrations/018_ai_cache_output_language.d.ts +10 -0
  114. package/backend/dist/db/migrations/018_ai_cache_output_language.d.ts.map +1 -0
  115. package/backend/dist/db/migrations/018_ai_cache_output_language.js +89 -0
  116. package/backend/dist/db/migrations/018_ai_cache_output_language.js.map +1 -0
  117. package/backend/dist/db/migrations/019_ai_suggestion_usage.d.ts +10 -0
  118. package/backend/dist/db/migrations/019_ai_suggestion_usage.d.ts.map +1 -0
  119. package/backend/dist/db/migrations/019_ai_suggestion_usage.js +47 -0
  120. package/backend/dist/db/migrations/019_ai_suggestion_usage.js.map +1 -0
  121. package/backend/dist/db/migrations/020_tenant_scope.d.ts +5 -0
  122. package/backend/dist/db/migrations/020_tenant_scope.d.ts.map +1 -0
  123. package/backend/dist/db/migrations/020_tenant_scope.js +105 -0
  124. package/backend/dist/db/migrations/020_tenant_scope.js.map +1 -0
  125. package/backend/dist/db/migrations/_legacy_cloud_010_organizations.d.ts +5 -0
  126. package/backend/dist/db/migrations/_legacy_cloud_010_organizations.d.ts.map +1 -0
  127. package/backend/dist/db/migrations/_legacy_cloud_010_organizations.js +113 -0
  128. package/backend/dist/db/migrations/_legacy_cloud_010_organizations.js.map +1 -0
  129. package/backend/dist/db/migrations/_legacy_cloud_011_org_scoped_teams.d.ts +11 -0
  130. package/backend/dist/db/migrations/_legacy_cloud_011_org_scoped_teams.d.ts.map +1 -0
  131. package/backend/dist/db/migrations/_legacy_cloud_011_org_scoped_teams.js +59 -0
  132. package/backend/dist/db/migrations/_legacy_cloud_011_org_scoped_teams.js.map +1 -0
  133. package/backend/dist/db/migrations/_legacy_cloud_012_org_invitations_token_hash.d.ts +10 -0
  134. package/backend/dist/db/migrations/_legacy_cloud_012_org_invitations_token_hash.d.ts.map +1 -0
  135. package/backend/dist/db/migrations/_legacy_cloud_012_org_invitations_token_hash.js +40 -0
  136. package/backend/dist/db/migrations/_legacy_cloud_012_org_invitations_token_hash.js.map +1 -0
  137. package/backend/dist/db/migrations/_legacy_cloud_016_free_plan_grace_ends_at.d.ts +9 -0
  138. package/backend/dist/db/migrations/_legacy_cloud_016_free_plan_grace_ends_at.d.ts.map +1 -0
  139. package/backend/dist/db/migrations/_legacy_cloud_016_free_plan_grace_ends_at.js +50 -0
  140. package/backend/dist/db/migrations/_legacy_cloud_016_free_plan_grace_ends_at.js.map +1 -0
  141. package/backend/dist/db/migrations/_legacy_cloud_017_ai_suggestions.d.ts +11 -0
  142. package/backend/dist/db/migrations/_legacy_cloud_017_ai_suggestions.d.ts.map +1 -0
  143. package/backend/dist/db/migrations/_legacy_cloud_017_ai_suggestions.js +78 -0
  144. package/backend/dist/db/migrations/_legacy_cloud_017_ai_suggestions.js.map +1 -0
  145. package/backend/dist/db/migrations/index.d.ts +22 -0
  146. package/backend/dist/db/migrations/index.d.ts.map +1 -0
  147. package/backend/dist/db/migrations/index.js +194 -0
  148. package/backend/dist/db/migrations/index.js.map +1 -0
  149. package/backend/dist/db/pool.d.ts +13 -0
  150. package/backend/dist/db/pool.d.ts.map +1 -0
  151. package/backend/dist/db/pool.js +41 -0
  152. package/backend/dist/db/pool.js.map +1 -0
  153. package/backend/dist/db/seed-data.d.ts +66 -0
  154. package/backend/dist/db/seed-data.d.ts.map +1 -0
  155. package/backend/dist/db/seed-data.js +394 -0
  156. package/backend/dist/db/seed-data.js.map +1 -0
  157. package/backend/dist/db/seed.d.ts +19 -0
  158. package/backend/dist/db/seed.d.ts.map +1 -0
  159. package/backend/dist/db/seed.js +406 -0
  160. package/backend/dist/db/seed.js.map +1 -0
  161. package/backend/dist/index.d.ts +3 -0
  162. package/backend/dist/index.d.ts.map +1 -0
  163. package/backend/dist/index.js +64 -0
  164. package/backend/dist/index.js.map +1 -0
  165. package/backend/dist/instrument.d.ts +2 -0
  166. package/backend/dist/instrument.d.ts.map +1 -0
  167. package/backend/dist/instrument.js +16 -0
  168. package/backend/dist/instrument.js.map +1 -0
  169. package/backend/dist/load-env.d.ts +2 -0
  170. package/backend/dist/load-env.d.ts.map +1 -0
  171. package/backend/dist/load-env.js +33 -0
  172. package/backend/dist/load-env.js.map +1 -0
  173. package/backend/dist/middleware/auth.d.ts +23 -0
  174. package/backend/dist/middleware/auth.d.ts.map +1 -0
  175. package/backend/dist/middleware/auth.js +69 -0
  176. package/backend/dist/middleware/auth.js.map +1 -0
  177. package/backend/dist/middleware/error-handler.d.ts +11 -0
  178. package/backend/dist/middleware/error-handler.d.ts.map +1 -0
  179. package/backend/dist/middleware/error-handler.js +40 -0
  180. package/backend/dist/middleware/error-handler.js.map +1 -0
  181. package/backend/dist/middleware/security.d.ts +26 -0
  182. package/backend/dist/middleware/security.d.ts.map +1 -0
  183. package/backend/dist/middleware/security.js +162 -0
  184. package/backend/dist/middleware/security.js.map +1 -0
  185. package/backend/dist/middleware/stats-auth.d.ts +7 -0
  186. package/backend/dist/middleware/stats-auth.d.ts.map +1 -0
  187. package/backend/dist/middleware/stats-auth.js +20 -0
  188. package/backend/dist/middleware/stats-auth.js.map +1 -0
  189. package/backend/dist/middleware/tenant.d.ts +3 -0
  190. package/backend/dist/middleware/tenant.d.ts.map +1 -0
  191. package/backend/dist/middleware/tenant.js +13 -0
  192. package/backend/dist/middleware/tenant.js.map +1 -0
  193. package/backend/dist/register-routes.d.ts +12 -0
  194. package/backend/dist/register-routes.d.ts.map +1 -0
  195. package/backend/dist/register-routes.js +59 -0
  196. package/backend/dist/register-routes.js.map +1 -0
  197. package/backend/dist/routes/admin/demo-reset.d.ts +8 -0
  198. package/backend/dist/routes/admin/demo-reset.d.ts.map +1 -0
  199. package/backend/dist/routes/admin/demo-reset.js +66 -0
  200. package/backend/dist/routes/admin/demo-reset.js.map +1 -0
  201. package/backend/dist/routes/admin/settings.d.ts +3 -0
  202. package/backend/dist/routes/admin/settings.d.ts.map +1 -0
  203. package/backend/dist/routes/admin/settings.js +452 -0
  204. package/backend/dist/routes/admin/settings.js.map +1 -0
  205. package/backend/dist/routes/admin/stats.d.ts +7 -0
  206. package/backend/dist/routes/admin/stats.d.ts.map +1 -0
  207. package/backend/dist/routes/admin/stats.js +66 -0
  208. package/backend/dist/routes/admin/stats.js.map +1 -0
  209. package/backend/dist/routes/admin/teams.d.ts +3 -0
  210. package/backend/dist/routes/admin/teams.d.ts.map +1 -0
  211. package/backend/dist/routes/admin/teams.js +509 -0
  212. package/backend/dist/routes/admin/teams.js.map +1 -0
  213. package/backend/dist/routes/admin/users.d.ts +3 -0
  214. package/backend/dist/routes/admin/users.d.ts.map +1 -0
  215. package/backend/dist/routes/admin/users.js +525 -0
  216. package/backend/dist/routes/admin/users.js.map +1 -0
  217. package/backend/dist/routes/auth.d.ts +3 -0
  218. package/backend/dist/routes/auth.d.ts.map +1 -0
  219. package/backend/dist/routes/auth.js +992 -0
  220. package/backend/dist/routes/auth.js.map +1 -0
  221. package/backend/dist/routes/billing.d.ts +8 -0
  222. package/backend/dist/routes/billing.d.ts.map +1 -0
  223. package/backend/dist/routes/billing.js +481 -0
  224. package/backend/dist/routes/billing.js.map +1 -0
  225. package/backend/dist/routes/bookmarks.d.ts +3 -0
  226. package/backend/dist/routes/bookmarks.d.ts.map +1 -0
  227. package/backend/dist/routes/bookmarks.js +1593 -0
  228. package/backend/dist/routes/bookmarks.js.map +1 -0
  229. package/backend/dist/routes/config.d.ts +7 -0
  230. package/backend/dist/routes/config.d.ts.map +1 -0
  231. package/backend/dist/routes/config.js +52 -0
  232. package/backend/dist/routes/config.js.map +1 -0
  233. package/backend/dist/routes/contact.d.ts +9 -0
  234. package/backend/dist/routes/contact.d.ts.map +1 -0
  235. package/backend/dist/routes/contact.js +99 -0
  236. package/backend/dist/routes/contact.js.map +1 -0
  237. package/backend/dist/routes/csrf.d.ts +3 -0
  238. package/backend/dist/routes/csrf.d.ts.map +1 -0
  239. package/backend/dist/routes/csrf.js +39 -0
  240. package/backend/dist/routes/csrf.js.map +1 -0
  241. package/backend/dist/routes/dashboard.d.ts +3 -0
  242. package/backend/dist/routes/dashboard.d.ts.map +1 -0
  243. package/backend/dist/routes/dashboard.js +212 -0
  244. package/backend/dist/routes/dashboard.js.map +1 -0
  245. package/backend/dist/routes/email-verification.d.ts +3 -0
  246. package/backend/dist/routes/email-verification.d.ts.map +1 -0
  247. package/backend/dist/routes/email-verification.js +124 -0
  248. package/backend/dist/routes/email-verification.js.map +1 -0
  249. package/backend/dist/routes/folders.d.ts +3 -0
  250. package/backend/dist/routes/folders.d.ts.map +1 -0
  251. package/backend/dist/routes/folders.js +524 -0
  252. package/backend/dist/routes/folders.js.map +1 -0
  253. package/backend/dist/routes/go-helpers.d.ts +18 -0
  254. package/backend/dist/routes/go-helpers.d.ts.map +1 -0
  255. package/backend/dist/routes/go-helpers.js +64 -0
  256. package/backend/dist/routes/go-helpers.js.map +1 -0
  257. package/backend/dist/routes/go.d.ts +23 -0
  258. package/backend/dist/routes/go.d.ts.map +1 -0
  259. package/backend/dist/routes/go.js +361 -0
  260. package/backend/dist/routes/go.js.map +1 -0
  261. package/backend/dist/routes/health.d.ts +6 -0
  262. package/backend/dist/routes/health.d.ts.map +1 -0
  263. package/backend/dist/routes/health.js +79 -0
  264. package/backend/dist/routes/health.js.map +1 -0
  265. package/backend/dist/routes/invitations.d.ts +3 -0
  266. package/backend/dist/routes/invitations.d.ts.map +1 -0
  267. package/backend/dist/routes/invitations.js +172 -0
  268. package/backend/dist/routes/invitations.js.map +1 -0
  269. package/backend/dist/routes/oidc-providers.d.ts +3 -0
  270. package/backend/dist/routes/oidc-providers.d.ts.map +1 -0
  271. package/backend/dist/routes/oidc-providers.js +495 -0
  272. package/backend/dist/routes/oidc-providers.js.map +1 -0
  273. package/backend/dist/routes/organizations.d.ts +3 -0
  274. package/backend/dist/routes/organizations.d.ts.map +1 -0
  275. package/backend/dist/routes/organizations.js +538 -0
  276. package/backend/dist/routes/organizations.js.map +1 -0
  277. package/backend/dist/routes/password-reset.d.ts +3 -0
  278. package/backend/dist/routes/password-reset.d.ts.map +1 -0
  279. package/backend/dist/routes/password-reset.js +212 -0
  280. package/backend/dist/routes/password-reset.js.map +1 -0
  281. package/backend/dist/routes/redirect.d.ts +3 -0
  282. package/backend/dist/routes/redirect.d.ts.map +1 -0
  283. package/backend/dist/routes/redirect.js +124 -0
  284. package/backend/dist/routes/redirect.js.map +1 -0
  285. package/backend/dist/routes/tags.d.ts +3 -0
  286. package/backend/dist/routes/tags.d.ts.map +1 -0
  287. package/backend/dist/routes/tags.js +302 -0
  288. package/backend/dist/routes/tags.js.map +1 -0
  289. package/backend/dist/routes/teams.d.ts +3 -0
  290. package/backend/dist/routes/teams.d.ts.map +1 -0
  291. package/backend/dist/routes/teams.js +60 -0
  292. package/backend/dist/routes/teams.js.map +1 -0
  293. package/backend/dist/routes/tokens.d.ts +3 -0
  294. package/backend/dist/routes/tokens.d.ts.map +1 -0
  295. package/backend/dist/routes/tokens.js +157 -0
  296. package/backend/dist/routes/tokens.js.map +1 -0
  297. package/backend/dist/routes/users.d.ts +3 -0
  298. package/backend/dist/routes/users.d.ts.map +1 -0
  299. package/backend/dist/routes/users.js +199 -0
  300. package/backend/dist/routes/users.js.map +1 -0
  301. package/backend/dist/services/ai-suggestions.d.ts +29 -0
  302. package/backend/dist/services/ai-suggestions.d.ts.map +1 -0
  303. package/backend/dist/services/ai-suggestions.js +163 -0
  304. package/backend/dist/services/ai-suggestions.js.map +1 -0
  305. package/backend/dist/services/api-tokens.d.ts +66 -0
  306. package/backend/dist/services/api-tokens.d.ts.map +1 -0
  307. package/backend/dist/services/api-tokens.js +129 -0
  308. package/backend/dist/services/api-tokens.js.map +1 -0
  309. package/backend/dist/services/fetch-page-metadata.d.ts +15 -0
  310. package/backend/dist/services/fetch-page-metadata.d.ts.map +1 -0
  311. package/backend/dist/services/fetch-page-metadata.js +205 -0
  312. package/backend/dist/services/fetch-page-metadata.js.map +1 -0
  313. package/backend/dist/services/stats.d.ts +73 -0
  314. package/backend/dist/services/stats.d.ts.map +1 -0
  315. package/backend/dist/services/stats.js +145 -0
  316. package/backend/dist/services/stats.js.map +1 -0
  317. package/backend/dist/types/oidc-provider.d.ts +17 -0
  318. package/backend/dist/types/oidc-provider.d.ts.map +1 -0
  319. package/backend/dist/types/oidc-provider.js +2 -0
  320. package/backend/dist/types/oidc-provider.js.map +1 -0
  321. package/backend/dist/types.d.ts +86 -0
  322. package/backend/dist/types.d.ts.map +1 -0
  323. package/backend/dist/types.js +2 -0
  324. package/backend/dist/types.js.map +1 -0
  325. package/backend/dist/utils/ai-feature.d.ts +23 -0
  326. package/backend/dist/utils/ai-feature.d.ts.map +1 -0
  327. package/backend/dist/utils/ai-feature.js +62 -0
  328. package/backend/dist/utils/ai-feature.js.map +1 -0
  329. package/backend/dist/utils/email.d.ts +40 -0
  330. package/backend/dist/utils/email.d.ts.map +1 -0
  331. package/backend/dist/utils/email.js +456 -0
  332. package/backend/dist/utils/email.js.map +1 -0
  333. package/backend/dist/utils/encryption.d.ts +18 -0
  334. package/backend/dist/utils/encryption.d.ts.map +1 -0
  335. package/backend/dist/utils/encryption.js +95 -0
  336. package/backend/dist/utils/encryption.js.map +1 -0
  337. package/backend/dist/utils/env-validation.d.ts +6 -0
  338. package/backend/dist/utils/env-validation.d.ts.map +1 -0
  339. package/backend/dist/utils/env-validation.js +51 -0
  340. package/backend/dist/utils/env-validation.js.map +1 -0
  341. package/backend/dist/utils/jwt.d.ts +20 -0
  342. package/backend/dist/utils/jwt.d.ts.map +1 -0
  343. package/backend/dist/utils/jwt.js +48 -0
  344. package/backend/dist/utils/jwt.js.map +1 -0
  345. package/backend/dist/utils/org-cleanup.d.ts +6 -0
  346. package/backend/dist/utils/org-cleanup.d.ts.map +1 -0
  347. package/backend/dist/utils/org-cleanup.js +37 -0
  348. package/backend/dist/utils/org-cleanup.js.map +1 -0
  349. package/backend/dist/utils/organizations.d.ts +12 -0
  350. package/backend/dist/utils/organizations.d.ts.map +1 -0
  351. package/backend/dist/utils/organizations.js +24 -0
  352. package/backend/dist/utils/organizations.js.map +1 -0
  353. package/backend/dist/utils/plan-errors.d.ts +18 -0
  354. package/backend/dist/utils/plan-errors.d.ts.map +1 -0
  355. package/backend/dist/utils/plan-errors.js +21 -0
  356. package/backend/dist/utils/plan-errors.js.map +1 -0
  357. package/backend/dist/utils/refresh-token.d.ts +31 -0
  358. package/backend/dist/utils/refresh-token.d.ts.map +1 -0
  359. package/backend/dist/utils/refresh-token.js +63 -0
  360. package/backend/dist/utils/refresh-token.js.map +1 -0
  361. package/backend/dist/utils/session-store.d.ts +46 -0
  362. package/backend/dist/utils/session-store.d.ts.map +1 -0
  363. package/backend/dist/utils/session-store.js +222 -0
  364. package/backend/dist/utils/session-store.js.map +1 -0
  365. package/backend/dist/utils/tenant.d.ts +5 -0
  366. package/backend/dist/utils/tenant.d.ts.map +1 -0
  367. package/backend/dist/utils/tenant.js +12 -0
  368. package/backend/dist/utils/tenant.js.map +1 -0
  369. package/backend/dist/utils/user-key.d.ts +24 -0
  370. package/backend/dist/utils/user-key.d.ts.map +1 -0
  371. package/backend/dist/utils/user-key.js +116 -0
  372. package/backend/dist/utils/user-key.js.map +1 -0
  373. package/backend/dist/utils/validation.d.ts +91 -0
  374. package/backend/dist/utils/validation.d.ts.map +1 -0
  375. package/backend/dist/utils/validation.js +337 -0
  376. package/backend/dist/utils/validation.js.map +1 -0
  377. package/backend/index.js +15 -0
  378. package/frontend/index.js +5 -0
  379. package/frontend/index.tsx +7 -0
  380. package/package.json +16 -0
  381. package/types/index.js +4 -0
@@ -0,0 +1,456 @@
1
+ import nodemailer from 'nodemailer';
2
+ import { queryOne } from '../db/index.js';
3
+ import { decrypt } from './encryption.js';
4
+ /**
5
+ * Escape HTML to prevent XSS attacks
6
+ */
7
+ function escapeHtml(unsafe) {
8
+ return unsafe
9
+ .replace(/&/g, '&')
10
+ .replace(/</g, '&lt;')
11
+ .replace(/>/g, '&gt;')
12
+ .replace(/"/g, '&quot;')
13
+ .replace(/'/g, '&#039;');
14
+ }
15
+ /**
16
+ * Validate and escape URL for safe use in HTML href attributes
17
+ * Note: href attributes need the URL as-is (not HTML-escaped) but display text should be escaped
18
+ */
19
+ function safeUrlForHref(url) {
20
+ // URL should already be validated before being passed here
21
+ // For href attributes, we can use the URL directly since it's validated
22
+ // But we'll double-check it doesn't contain javascript: or data: protocols
23
+ const lowerUrl = url.toLowerCase().trim();
24
+ if (lowerUrl.startsWith('javascript:') || lowerUrl.startsWith('data:') || lowerUrl.startsWith('vbscript:')) {
25
+ // This should never happen if validation is working, but be defensive
26
+ return '#';
27
+ }
28
+ return url;
29
+ }
30
+ /** Primary brand color (blue-600) matching app style */
31
+ const PRIMARY_BLUE = '#2563eb';
32
+ function getFrontendUrl() {
33
+ return (process.env.FRONTEND_URL || 'http://localhost:3000').replace(/\/$/, '');
34
+ }
35
+ /**
36
+ * Build a consistent email layout with header (logo + SlugBase), content, and footer.
37
+ */
38
+ function buildEmailLayout(options) {
39
+ const { contentHtml, title = 'SlugBase', footerMessage = 'This is an automated message from SlugBase. Please do not reply to this email.', includeLegalFooter = false, } = options;
40
+ const frontendUrl = getFrontendUrl();
41
+ const logoUrl = `${frontendUrl}/slugbase_icon_white.svg`;
42
+ const legalFooterHtml = includeLegalFooter
43
+ ? ` &middot; <a href="${frontendUrl}/imprint" style="color: #6b7280; text-decoration: underline;">Imprint</a> &middot; <a href="${frontendUrl}/privacy" style="color: #6b7280; text-decoration: underline;">Privacy</a>`
44
+ : '';
45
+ return `<!DOCTYPE html>
46
+ <html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
47
+ <head>
48
+ <meta charset="utf-8">
49
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
50
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
51
+ <meta name="x-apple-disable-message-reformatting">
52
+ <title>${escapeHtml(title)}</title>
53
+ <!--[if mso]>
54
+ <noscript>
55
+ <xml>
56
+ <o:OfficeDocumentSettings>
57
+ <o:PixelsPerInch>96</o:PixelsPerInch>
58
+ </o:OfficeDocumentSettings>
59
+ </xml>
60
+ </noscript>
61
+ <![endif]-->
62
+ </head>
63
+ <body style="margin: 0; padding: 0; background-color: #f4f4f4; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;">
64
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: #f4f4f4;">
65
+ <tr>
66
+ <td align="center" style="padding: 40px 20px;">
67
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="600" style="max-width: 600px; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
68
+ <!-- Header -->
69
+ <tr>
70
+ <td style="padding: 32px 40px; text-align: center; background-color: ${PRIMARY_BLUE}; border-radius: 8px 8px 0 0;">
71
+ <img src="${logoUrl}" alt="SlugBase" width="40" height="40" style="display: inline-block; vertical-align: middle; margin-right: 10px;" />
72
+ <h1 style="margin: 0; display: inline-block; vertical-align: middle; color: #ffffff; font-size: 28px; font-weight: 600; letter-spacing: -0.5px;">SlugBase</h1>
73
+ </td>
74
+ </tr>
75
+ <!-- Content -->
76
+ <tr>
77
+ <td style="padding: 40px;">
78
+ ${contentHtml}
79
+ </td>
80
+ </tr>
81
+ <!-- Footer -->
82
+ <tr>
83
+ <td style="padding: 30px 40px; background-color: #f9fafb; border-top: 1px solid #e5e7eb; border-radius: 0 0 8px 8px;">
84
+ <p style="margin: 0; color: #9ca3af; font-size: 12px; line-height: 1.6; text-align: center;">${footerMessage}${legalFooterHtml}</p>
85
+ </td>
86
+ </tr>
87
+ </table>
88
+ </td>
89
+ </tr>
90
+ </table>
91
+ </body>
92
+ </html>`;
93
+ }
94
+ /**
95
+ * Get SMTP configuration from system settings (SELFHOSTED only).
96
+ * Returns config object or null with error message.
97
+ */
98
+ async function getSMTPConfig() {
99
+ try {
100
+ const enabled = await queryOne('SELECT value FROM system_config WHERE key = ?', ['smtp_enabled']);
101
+ if (!enabled || enabled.value !== 'true') {
102
+ return { config: null, error: 'SMTP is not enabled' };
103
+ }
104
+ const host = await queryOne('SELECT value FROM system_config WHERE key = ?', ['smtp_host']);
105
+ const port = await queryOne('SELECT value FROM system_config WHERE key = ?', ['smtp_port']);
106
+ const secure = await queryOne('SELECT value FROM system_config WHERE key = ?', ['smtp_secure']);
107
+ const user = await queryOne('SELECT value FROM system_config WHERE key = ?', ['smtp_user']);
108
+ const password = await queryOne('SELECT value FROM system_config WHERE key = ?', ['smtp_password']);
109
+ const from = await queryOne('SELECT value FROM system_config WHERE key = ?', ['smtp_from']);
110
+ const fromName = await queryOne('SELECT value FROM system_config WHERE key = ?', ['smtp_from_name']);
111
+ if (!host)
112
+ return { config: null, error: 'SMTP host is not configured' };
113
+ if (!port)
114
+ return { config: null, error: 'SMTP port is not configured' };
115
+ if (!user)
116
+ return { config: null, error: 'SMTP user is not configured' };
117
+ if (!password)
118
+ return { config: null, error: 'SMTP password is not configured' };
119
+ if (!from)
120
+ return { config: null, error: 'SMTP from email is not configured' };
121
+ const hostValue = String(host.value).trim();
122
+ const userValue = String(user.value).trim();
123
+ const passwordValue = String(password.value).trim();
124
+ const fromValue = String(from.value).trim();
125
+ if (!hostValue)
126
+ return { config: null, error: 'SMTP host is empty' };
127
+ if (!userValue)
128
+ return { config: null, error: 'SMTP user is empty' };
129
+ if (!passwordValue)
130
+ return { config: null, error: 'SMTP password is empty' };
131
+ if (!fromValue)
132
+ return { config: null, error: 'SMTP from email is empty' };
133
+ let decryptedPassword = passwordValue;
134
+ try {
135
+ decryptedPassword = decrypt(passwordValue);
136
+ }
137
+ catch (error) {
138
+ console.warn('SMTP password decryption failed, using as plain text:', error.message);
139
+ decryptedPassword = passwordValue;
140
+ }
141
+ const trimmedPassword = decryptedPassword ? decryptedPassword.trim() : '';
142
+ if (!trimmedPassword) {
143
+ console.error('SMTP password validation failed: password is empty');
144
+ return { config: null, error: 'SMTP password is empty. Please set a password in the SMTP settings.' };
145
+ }
146
+ return {
147
+ config: {
148
+ host: hostValue,
149
+ port: parseInt(String(port.value)) || 587,
150
+ secure: secure?.value === 'true' || false,
151
+ auth: { user: userValue, password: trimmedPassword },
152
+ from: fromValue,
153
+ fromName: String(fromName?.value || 'SlugBase'),
154
+ },
155
+ };
156
+ }
157
+ catch (error) {
158
+ console.error('Error getting SMTP config:', error);
159
+ return { config: null, error: `Error retrieving SMTP config: ${error.message}` };
160
+ }
161
+ }
162
+ /**
163
+ * Send email using SMTP (self-hosted).
164
+ */
165
+ export async function sendEmail(to, subject, html, text) {
166
+ try {
167
+ const { config, error } = await getSMTPConfig();
168
+ if (!config) {
169
+ return { success: false, error: error || 'SMTP not configured or not enabled' };
170
+ }
171
+ // Validate auth credentials before creating transporter
172
+ if (!config.auth || !config.auth.user || !config.auth.password) {
173
+ return { success: false, error: 'SMTP auth credentials are missing' };
174
+ }
175
+ const authUser = config.auth.user.trim();
176
+ const authPassword = config.auth.password.trim();
177
+ if (!authUser || !authPassword) {
178
+ console.error('SMTP auth validation failed: credentials are empty');
179
+ return { success: false, error: 'SMTP auth credentials are empty' };
180
+ }
181
+ const transportConfig = {
182
+ host: config.host,
183
+ port: config.port,
184
+ secure: config.secure,
185
+ auth: { user: authUser, pass: authPassword },
186
+ };
187
+ console.log('Creating SMTP transporter with:', { host: config.host, port: config.port, secure: config.secure });
188
+ const transporter = nodemailer.createTransport(transportConfig);
189
+ const stripHtml = (htmlContent) => {
190
+ if (htmlContent.length > 100000)
191
+ htmlContent = htmlContent.substring(0, 100000);
192
+ return htmlContent.replace(/<[^>]{0,1000}>/g, '');
193
+ };
194
+ const info = await transporter.sendMail({
195
+ from: `"${config.fromName}" <${config.from}>`,
196
+ to,
197
+ subject,
198
+ text: text || stripHtml(html),
199
+ html,
200
+ });
201
+ console.log('Email sent:', info.messageId);
202
+ return { success: true };
203
+ }
204
+ catch (error) {
205
+ console.error('Error sending email:', error);
206
+ return { success: false, error: error.message || 'Unknown error sending email' };
207
+ }
208
+ }
209
+ /**
210
+ * Send password reset email
211
+ */
212
+ export async function sendPasswordResetEmail(email, resetToken, resetUrl) {
213
+ const safeHrefUrl = safeUrlForHref(resetUrl);
214
+ const escapedDisplayUrl = escapeHtml(resetUrl);
215
+ const subject = 'Password Reset Request - SlugBase';
216
+ const contentHtml = `
217
+ <h2 style="margin: 0 0 20px; color: #1a1a1a; font-size: 24px; font-weight: 600; line-height: 1.3;">Password Reset Request</h2>
218
+ <p style="margin: 0 0 20px; color: #4a4a4a; font-size: 16px; line-height: 1.6;">You requested to reset your password for your SlugBase account. Click the button below to create a new password:</p>
219
+
220
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: 30px 0;">
221
+ <tr>
222
+ <td align="center" style="padding: 0;">
223
+ <a href="${safeHrefUrl}" style="display: inline-block; padding: 14px 32px; background-color: ${PRIMARY_BLUE}; color: #ffffff; text-decoration: none; border-radius: 6px; font-size: 16px; font-weight: 600; text-align: center; box-shadow: 0 2px 4px rgba(37, 99, 235, 0.3);">Reset Password</a>
224
+ </td>
225
+ </tr>
226
+ </table>
227
+
228
+ <p style="margin: 20px 0; color: #6b7280; font-size: 14px; line-height: 1.6;">Or copy and paste this link into your browser:</p>
229
+ <p style="margin: 0 0 30px; padding: 12px; background-color: #f9fafb; border-radius: 4px; word-break: break-all; color: #4a4a4a; font-size: 13px; font-family: 'Courier New', monospace; line-height: 1.5;">${escapedDisplayUrl}</p>
230
+
231
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: 30px 0; background-color: #fef3c7; border-left: 4px solid #f59e0b; border-radius: 4px;">
232
+ <tr>
233
+ <td style="padding: 16px;">
234
+ <p style="margin: 0; color: #92400e; font-size: 14px; line-height: 1.6; font-weight: 500;">⚠️ This link will expire in 1 hour for security reasons.</p>
235
+ </td>
236
+ </tr>
237
+ </table>
238
+
239
+ <p style="margin: 30px 0 0; color: #6b7280; font-size: 14px; line-height: 1.6;">If you did not request a password reset, please ignore this email. Your password will remain unchanged.</p>
240
+ `;
241
+ const html = buildEmailLayout({
242
+ contentHtml,
243
+ title: 'Password Reset Request',
244
+ includeLegalFooter: true,
245
+ });
246
+ const result = await sendEmail(email, subject, html);
247
+ return result.success;
248
+ }
249
+ /**
250
+ * Send email verification email
251
+ */
252
+ export async function sendEmailVerificationEmail(email, verificationToken, verificationUrl, newEmail) {
253
+ const safeHrefUrl = safeUrlForHref(verificationUrl);
254
+ const escapedDisplayUrl = escapeHtml(verificationUrl);
255
+ const escapedNewEmail = escapeHtml(newEmail);
256
+ const subject = 'Verify Your New Email Address - SlugBase';
257
+ const contentHtml = `
258
+ <h2 style="margin: 0 0 20px; color: #1a1a1a; font-size: 24px; font-weight: 600; line-height: 1.3;">Verify Your New Email Address</h2>
259
+ <p style="margin: 0 0 20px; color: #4a4a4a; font-size: 16px; line-height: 1.6;">You requested to change your email address to <strong style="color: #1a1a1a;">${escapedNewEmail}</strong>. Click the button below to verify and complete the change:</p>
260
+
261
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: 30px 0;">
262
+ <tr>
263
+ <td align="center" style="padding: 0;">
264
+ <a href="${safeHrefUrl}" style="display: inline-block; padding: 14px 32px; background-color: ${PRIMARY_BLUE}; color: #ffffff; text-decoration: none; border-radius: 6px; font-size: 16px; font-weight: 600; text-align: center; box-shadow: 0 2px 4px rgba(37, 99, 235, 0.3);">Verify Email Address</a>
265
+ </td>
266
+ </tr>
267
+ </table>
268
+
269
+ <p style="margin: 20px 0; color: #6b7280; font-size: 14px; line-height: 1.6;">Or copy and paste this link into your browser:</p>
270
+ <p style="margin: 0 0 30px; padding: 12px; background-color: #f9fafb; border-radius: 4px; word-break: break-all; color: #4a4a4a; font-size: 13px; font-family: 'Courier New', monospace; line-height: 1.5;">${escapedDisplayUrl}</p>
271
+
272
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: 30px 0; background-color: #dbeafe; border-left: 4px solid ${PRIMARY_BLUE}; border-radius: 4px;">
273
+ <tr>
274
+ <td style="padding: 16px;">
275
+ <p style="margin: 0; color: #1e40af; font-size: 14px; line-height: 1.6; font-weight: 500;">ℹ️ This link will expire in 24 hours for security reasons.</p>
276
+ </td>
277
+ </tr>
278
+ </table>
279
+
280
+ <p style="margin: 30px 0 0; color: #6b7280; font-size: 14px; line-height: 1.6;">If you did not request this email change, please ignore this email. Your email address will remain unchanged.</p>
281
+ `;
282
+ const html = buildEmailLayout({
283
+ contentHtml,
284
+ title: 'Verify Your New Email Address',
285
+ includeLegalFooter: true,
286
+ });
287
+ const result = await sendEmail(email, subject, html);
288
+ return result.success;
289
+ }
290
+ /**
291
+ * Send contact form confirmation to the customer
292
+ */
293
+ export async function sendContactConfirmationEmail(email, name) {
294
+ const escapedName = escapeHtml(name);
295
+ const escapedEmail = escapeHtml(email);
296
+ const subject = 'We received your message - SlugBase';
297
+ const contentHtml = `
298
+ <h2 style="margin: 0 0 20px; color: #1a1a1a; font-size: 24px; font-weight: 600;">We received your message</h2>
299
+ <p style="margin: 0 0 20px; color: #4a4a4a; font-size: 16px; line-height: 1.6;">Hi ${escapedName},</p>
300
+ <p style="margin: 0 0 20px; color: #4a4a4a; font-size: 16px; line-height: 1.6;">Thank you for reaching out! We have received your message and will get back to you at ${escapedEmail} as soon as possible.</p>
301
+ <p style="margin: 30px 0 0; color: #6b7280; font-size: 14px; line-height: 1.6;">Best regards,<br>The SlugBase Team</p>
302
+ `;
303
+ const html = buildEmailLayout({
304
+ contentHtml,
305
+ title: 'Message Received',
306
+ footerMessage: 'This is an automated confirmation from SlugBase.',
307
+ includeLegalFooter: true,
308
+ });
309
+ const result = await sendEmail(email, subject, html);
310
+ return result.success;
311
+ }
312
+ /**
313
+ * Send contact form submission to the configured recipient
314
+ */
315
+ export async function sendContactFormNotification(recipient, data) {
316
+ const escapedName = escapeHtml(data.name);
317
+ const escapedEmail = escapeHtml(data.email);
318
+ const escapedMessage = escapeHtml(data.message).replace(/\n/g, '<br>');
319
+ const subject = `Contact Form: Message from ${escapedName}`;
320
+ const contentHtml = `
321
+ <h2 style="margin: 0 0 20px; color: #1a1a1a; font-size: 24px; font-weight: 600;">New Contact Form Submission</h2>
322
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: 0 0 20px; background-color: #f9fafb; border-radius: 6px; border: 1px solid #e5e7eb;">
323
+ <tr>
324
+ <td style="padding: 16px;">
325
+ <p style="margin: 0 0 8px; color: #6b7280; font-size: 12px; font-weight: 600; text-transform: uppercase;">Name</p>
326
+ <p style="margin: 0; color: #1a1a1a; font-size: 16px;">${escapedName}</p>
327
+ </td>
328
+ </tr>
329
+ <tr>
330
+ <td style="padding: 16px; border-top: 1px solid #e5e7eb;">
331
+ <p style="margin: 0 0 8px; color: #6b7280; font-size: 12px; font-weight: 600; text-transform: uppercase;">Email</p>
332
+ <p style="margin: 0;"><a href="mailto:${escapedEmail}" style="color: ${PRIMARY_BLUE}; text-decoration: none;">${escapedEmail}</a></p>
333
+ </td>
334
+ </tr>
335
+ <tr>
336
+ <td style="padding: 16px; border-top: 1px solid #e5e7eb;">
337
+ <p style="margin: 0 0 8px; color: #6b7280; font-size: 12px; font-weight: 600; text-transform: uppercase;">Message</p>
338
+ <p style="margin: 0; color: #1a1a1a; font-size: 16px; line-height: 1.6; white-space: pre-wrap;">${escapedMessage}</p>
339
+ </td>
340
+ </tr>
341
+ </table>
342
+ <p style="margin: 20px 0 0; color: #9ca3af; font-size: 12px;">Submitted via SlugBase contact form</p>
343
+ `;
344
+ const html = buildEmailLayout({
345
+ contentHtml,
346
+ title: 'Contact Form Submission',
347
+ footerMessage: 'This is an automated notification from SlugBase.',
348
+ includeLegalFooter: false,
349
+ });
350
+ const result = await sendEmail(recipient, subject, html);
351
+ return result.success;
352
+ }
353
+ /**
354
+ * Send signup verification email.
355
+ */
356
+ export async function sendSignupVerificationEmail(email, verificationUrl) {
357
+ const safeHrefUrl = safeUrlForHref(verificationUrl);
358
+ const escapedDisplayUrl = escapeHtml(verificationUrl);
359
+ const subject = 'Verify your SlugBase account';
360
+ const contentHtml = `
361
+ <h2 style="margin: 0 0 20px; color: #1a1a1a; font-size: 24px; font-weight: 600;">Verify your account</h2>
362
+ <p style="margin: 0 0 20px; color: #4a4a4a; font-size: 16px; line-height: 1.6;">Thanks for signing up. Click the button below to verify your email and start using SlugBase:</p>
363
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: 30px 0;">
364
+ <tr>
365
+ <td align="center" style="padding: 0;">
366
+ <a href="${safeHrefUrl}" style="display: inline-block; padding: 14px 32px; background-color: ${PRIMARY_BLUE}; color: #ffffff; text-decoration: none; border-radius: 6px; font-size: 16px; font-weight: 600;">Verify email</a>
367
+ </td>
368
+ </tr>
369
+ </table>
370
+ <p style="margin: 20px 0; color: #6b7280; font-size: 14px;">Or copy and paste this link into your browser:</p>
371
+ <p style="margin: 0 0 30px; padding: 12px; background-color: #f9fafb; border-radius: 4px; word-break: break-all; color: #4a4a4a; font-size: 13px; font-family: monospace;">${escapedDisplayUrl}</p>
372
+ <p style="margin: 0; color: #6b7280; font-size: 14px;">This link expires in 24 hours. If you did not sign up for SlugBase, you can ignore this email.</p>
373
+ `;
374
+ const html = buildEmailLayout({
375
+ contentHtml,
376
+ title: 'Verify your SlugBase account',
377
+ includeLegalFooter: true,
378
+ });
379
+ const result = await sendEmail(email, subject, html);
380
+ return result.success;
381
+ }
382
+ /**
383
+ * Test SMTP configuration
384
+ */
385
+ export async function testSMTPConfig(testEmail) {
386
+ try {
387
+ const { config, error } = await getSMTPConfig();
388
+ if (!config) {
389
+ return { success: false, error: error || 'SMTP not configured or not enabled' };
390
+ }
391
+ const escapedTestEmail = escapeHtml(testEmail);
392
+ const subject = 'SMTP Test Email - SlugBase';
393
+ const sentDate = new Date().toLocaleString('en-US', {
394
+ weekday: 'long',
395
+ year: 'numeric',
396
+ month: 'long',
397
+ day: 'numeric',
398
+ hour: '2-digit',
399
+ minute: '2-digit',
400
+ timeZoneName: 'short',
401
+ });
402
+ const contentHtml = `
403
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: 0 0 30px; background-color: #d1fae5; border-left: 4px solid #10b981; border-radius: 4px;">
404
+ <tr>
405
+ <td style="padding: 20px; text-align: center;">
406
+ <p style="margin: 0; color: #065f46; font-size: 48px; line-height: 1;">✓</p>
407
+ </td>
408
+ </tr>
409
+ </table>
410
+
411
+ <h2 style="margin: 0 0 20px; color: #1a1a1a; font-size: 24px; font-weight: 600; line-height: 1.3; text-align: center;">SMTP Configuration Test</h2>
412
+ <p style="margin: 0 0 30px; color: #4a4a4a; font-size: 16px; line-height: 1.6; text-align: center;">If you received this email, your SMTP configuration is working correctly!</p>
413
+
414
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: 30px 0; background-color: #f9fafb; border-radius: 6px; border: 1px solid #e5e7eb;">
415
+ <tr>
416
+ <td style="padding: 20px;">
417
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
418
+ <tr>
419
+ <td style="padding: 0 0 12px; color: #6b7280; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">Test Details</td>
420
+ </tr>
421
+ <tr>
422
+ <td style="padding: 0 0 8px; color: #1a1a1a; font-size: 14px; line-height: 1.6;">
423
+ <strong style="color: #4a4a4a;">Sent at:</strong> ${sentDate}
424
+ </td>
425
+ </tr>
426
+ <tr>
427
+ <td style="padding: 0; color: #1a1a1a; font-size: 14px; line-height: 1.6;">
428
+ <strong style="color: #4a4a4a;">Recipient:</strong> ${escapedTestEmail}
429
+ </td>
430
+ </tr>
431
+ </table>
432
+ </td>
433
+ </tr>
434
+ </table>
435
+
436
+ <p style="margin: 30px 0 0; color: #6b7280; font-size: 14px; line-height: 1.6; text-align: center;">Your email server is properly configured and ready to send emails from SlugBase.</p>
437
+ `;
438
+ const html = buildEmailLayout({
439
+ contentHtml,
440
+ title: 'SMTP Test Email',
441
+ footerMessage: 'This is a test email from SlugBase. Your SMTP configuration is working correctly.',
442
+ includeLegalFooter: false,
443
+ });
444
+ const result = await sendEmail(testEmail, subject, html);
445
+ if (result.success) {
446
+ return { success: true };
447
+ }
448
+ else {
449
+ return { success: false, error: result.error || 'Failed to send test email' };
450
+ }
451
+ }
452
+ catch (error) {
453
+ return { success: false, error: error.message || 'Unknown error' };
454
+ }
455
+ }
456
+ //# sourceMappingURL=email.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email.js","sourceRoot":"","sources":["../../src/utils/email.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Encrypt sensitive data
3
+ * @param text - Plain text to encrypt
4
+ * @returns Encrypted string in format: salt:iv:tag:encryptedData (all hex encoded)
5
+ */
6
+ export declare function encrypt(text: string): string;
7
+ /**
8
+ * Decrypt sensitive data
9
+ * @param encryptedText - Encrypted string in format: salt:iv:tag:encryptedData
10
+ * @returns Decrypted plain text
11
+ */
12
+ export declare function decrypt(encryptedText: string): string;
13
+ /**
14
+ * Generate a secure encryption key (for use in ENCRYPTION_KEY env var)
15
+ * @returns Hex-encoded 32-byte key
16
+ */
17
+ export declare function generateEncryptionKey(): string;
18
+ //# sourceMappingURL=encryption.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryption.d.ts","sourceRoot":"","sources":["../../src/utils/encryption.ts"],"names":[],"mappings":"AAoCA;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqB5C;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAkCrD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C"}
@@ -0,0 +1,95 @@
1
+ import crypto from 'crypto';
2
+ const ALGORITHM = 'aes-256-gcm';
3
+ const IV_LENGTH = 16;
4
+ const SALT_LENGTH = 64;
5
+ const TAG_LENGTH = 16;
6
+ const KEY_LENGTH = 32;
7
+ /**
8
+ * Get encryption key from environment variable
9
+ * ENCRYPTION_KEY is validated at startup via validateEnvironmentVariables()
10
+ * This will throw if not set, preventing insecure defaults
11
+ */
12
+ function getEncryptionKey() {
13
+ const key = process.env.ENCRYPTION_KEY;
14
+ if (!key) {
15
+ throw new Error('ENCRYPTION_KEY environment variable is required. Please set it before starting the server.');
16
+ }
17
+ // If key is provided as hex string (64 chars = 32 bytes), convert it
18
+ if (key.length === 64) {
19
+ try {
20
+ return Buffer.from(key, 'hex');
21
+ }
22
+ catch (error) {
23
+ throw new Error('ENCRYPTION_KEY must be a valid hex string (64 characters) or a string of at least 32 characters');
24
+ }
25
+ }
26
+ // Otherwise, derive key from the provided string
27
+ if (key.length < 32) {
28
+ throw new Error('ENCRYPTION_KEY must be at least 32 characters long');
29
+ }
30
+ return crypto.scryptSync(key, 'slugbase-salt', KEY_LENGTH);
31
+ }
32
+ /**
33
+ * Encrypt sensitive data
34
+ * @param text - Plain text to encrypt
35
+ * @returns Encrypted string in format: salt:iv:tag:encryptedData (all hex encoded)
36
+ */
37
+ export function encrypt(text) {
38
+ if (!text) {
39
+ return text;
40
+ }
41
+ const key = getEncryptionKey();
42
+ const iv = crypto.randomBytes(IV_LENGTH);
43
+ const salt = crypto.randomBytes(SALT_LENGTH);
44
+ // Derive key from master key and salt
45
+ const derivedKey = crypto.scryptSync(key, salt, KEY_LENGTH);
46
+ const cipher = crypto.createCipheriv(ALGORITHM, derivedKey, iv);
47
+ let encrypted = cipher.update(text, 'utf8', 'hex');
48
+ encrypted += cipher.final('hex');
49
+ const tag = cipher.getAuthTag();
50
+ // Return format: salt:iv:tag:encryptedData
51
+ return `${salt.toString('hex')}:${iv.toString('hex')}:${tag.toString('hex')}:${encrypted}`;
52
+ }
53
+ /**
54
+ * Decrypt sensitive data
55
+ * @param encryptedText - Encrypted string in format: salt:iv:tag:encryptedData
56
+ * @returns Decrypted plain text
57
+ */
58
+ export function decrypt(encryptedText) {
59
+ if (!encryptedText) {
60
+ return encryptedText;
61
+ }
62
+ // Check if this is already decrypted (for migration purposes)
63
+ // Encrypted strings have the format: salt:iv:tag:data (all hex, separated by colons)
64
+ const parts = encryptedText.split(':');
65
+ if (parts.length !== 4) {
66
+ // Assume it's plain text (for backward compatibility during migration)
67
+ return encryptedText;
68
+ }
69
+ try {
70
+ const [saltHex, ivHex, tagHex, encrypted] = parts;
71
+ const salt = Buffer.from(saltHex, 'hex');
72
+ const iv = Buffer.from(ivHex, 'hex');
73
+ const tag = Buffer.from(tagHex, 'hex');
74
+ const key = getEncryptionKey();
75
+ const derivedKey = crypto.scryptSync(key, salt, KEY_LENGTH);
76
+ const decipher = crypto.createDecipheriv(ALGORITHM, derivedKey, iv);
77
+ decipher.setAuthTag(tag);
78
+ let decrypted = decipher.update(encrypted, 'hex', 'utf8');
79
+ decrypted += decipher.final('utf8');
80
+ return decrypted;
81
+ }
82
+ catch (error) {
83
+ // If decryption fails, assume it's plain text (for migration)
84
+ console.warn('Decryption failed, assuming plain text:', error);
85
+ return encryptedText;
86
+ }
87
+ }
88
+ /**
89
+ * Generate a secure encryption key (for use in ENCRYPTION_KEY env var)
90
+ * @returns Hex-encoded 32-byte key
91
+ */
92
+ export function generateEncryptionKey() {
93
+ return crypto.randomBytes(KEY_LENGTH).toString('hex');
94
+ }
95
+ //# sourceMappingURL=encryption.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryption.js","sourceRoot":"","sources":["../../src/utils/encryption.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Environment variable validation
3
+ * Ensures all required security-related environment variables are set
4
+ */
5
+ export declare function validateEnvironmentVariables(): void;
6
+ //# sourceMappingURL=env-validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-validation.d.ts","sourceRoot":"","sources":["../../src/utils/env-validation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,wBAAgB,4BAA4B,IAAI,IAAI,CAgDnD"}