@sonicjs-cms/core 2.7.0 → 2.8.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/dist/{app-DV27cjPy.d.cts → app-CYEm1ytG.d.cts} +1 -0
- package/dist/{app-DV27cjPy.d.ts → app-CYEm1ytG.d.ts} +1 -0
- package/dist/{chunk-DNHJS6RN.js → chunk-34QIAULP.js} +4 -4
- package/dist/{chunk-DNHJS6RN.js.map → chunk-34QIAULP.js.map} +1 -1
- package/dist/{chunk-Y3EWJQ4D.js → chunk-3E76TKR5.js} +3 -3
- package/dist/{chunk-Y3EWJQ4D.js.map → chunk-3E76TKR5.js.map} +1 -1
- package/dist/{chunk-YRFAQ6MI.cjs → chunk-5CENPGR2.cjs} +219 -14
- package/dist/chunk-5CENPGR2.cjs.map +1 -0
- package/dist/{chunk-MYB5RY7H.cjs → chunk-5HMR2SJW.cjs} +4 -4
- package/dist/{chunk-MYB5RY7H.cjs.map → chunk-5HMR2SJW.cjs.map} +1 -1
- package/dist/{chunk-YHW27CBV.cjs → chunk-6FHNRRJ3.cjs} +190 -2
- package/dist/chunk-6FHNRRJ3.cjs.map +1 -0
- package/dist/{chunk-UISZ2MBW.js → chunk-BAWMAS5S.js} +5438 -1443
- package/dist/chunk-BAWMAS5S.js.map +1 -0
- package/dist/{chunk-F332TENF.js → chunk-CJYFSKH7.js} +4 -190
- package/dist/chunk-CJYFSKH7.js.map +1 -0
- package/dist/{chunk-3YNNVSMC.js → chunk-G44QUVNM.js} +90 -2
- package/dist/chunk-G44QUVNM.js.map +1 -0
- package/dist/{chunk-E2BXLXPW.cjs → chunk-GPTMGUFN.cjs} +4 -4
- package/dist/{chunk-E2BXLXPW.cjs.map → chunk-GPTMGUFN.cjs.map} +1 -1
- package/dist/chunk-H7AMQWVI.js +2466 -0
- package/dist/chunk-H7AMQWVI.js.map +1 -0
- package/dist/{chunk-CLIH2T74.js → chunk-J5WGMRSU.js} +189 -3
- package/dist/chunk-J5WGMRSU.js.map +1 -0
- package/dist/{chunk-L2IDZI7F.js → chunk-JDFPB6UW.js} +219 -14
- package/dist/chunk-JDFPB6UW.js.map +1 -0
- package/dist/{chunk-Y72M3MVX.cjs → chunk-MNFY6DWY.cjs} +13 -200
- package/dist/chunk-MNFY6DWY.cjs.map +1 -0
- package/dist/chunk-S6K2H2TS.cjs +2470 -0
- package/dist/chunk-S6K2H2TS.cjs.map +1 -0
- package/dist/{chunk-EHSZ6TAN.cjs → chunk-SHCYIZAN.cjs} +9 -2
- package/dist/chunk-SHCYIZAN.cjs.map +1 -0
- package/dist/{chunk-GRN3GHUG.js → chunk-VCH6HXVP.js} +9 -2
- package/dist/chunk-VCH6HXVP.js.map +1 -0
- package/dist/{chunk-7FOAMNTI.cjs → chunk-VNLR35GO.cjs} +90 -2
- package/dist/chunk-VNLR35GO.cjs.map +1 -0
- package/dist/{chunk-J7F3NPAP.cjs → chunk-YE2MU7CN.cjs} +5192 -1194
- package/dist/chunk-YE2MU7CN.cjs.map +1 -0
- package/dist/index.cjs +201 -607
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +67 -473
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +23 -23
- package/dist/middleware.d.cts +1 -1
- package/dist/middleware.d.ts +1 -1
- package/dist/middleware.js +2 -2
- package/dist/migrations-7JGSFOCM.cjs +13 -0
- package/dist/{migrations-LEMFV2ND.cjs.map → migrations-7JGSFOCM.cjs.map} +1 -1
- package/dist/migrations-YB77VTVF.js +4 -0
- package/dist/{migrations-RKQES6XY.js.map → migrations-YB77VTVF.js.map} +1 -1
- package/dist/{plugin-bootstrap-CB-xaBfK.d.ts → plugin-bootstrap-C7Mj00Ud.d.ts} +2455 -1
- package/dist/{plugin-bootstrap-U-cw9jn3.d.cts → plugin-bootstrap-DKB5f8-E.d.cts} +2455 -1
- package/dist/plugins.cjs +14 -14
- package/dist/plugins.js +2 -2
- package/dist/routes.cjs +39 -27
- package/dist/routes.d.cts +126 -53
- package/dist/routes.d.ts +126 -53
- package/dist/routes.js +7 -7
- package/dist/services.cjs +14 -14
- package/dist/services.d.cts +1 -1
- package/dist/services.d.ts +1 -1
- package/dist/services.js +2 -2
- package/dist/templates.cjs +25 -17
- package/dist/templates.d.cts +21 -1
- package/dist/templates.d.ts +21 -1
- package/dist/templates.js +2 -2
- package/dist/utils.cjs +14 -14
- package/dist/utils.js +1 -1
- package/migrations/014_fix_plugin_registry.sql +1 -1
- package/migrations/020_add_email_plugin.sql +1 -1
- package/migrations/026_add_otp_login.sql +1 -1
- package/migrations/029_add_forms_system.sql +184 -0
- package/migrations/030_add_turnstile_to_forms.sql +14 -0
- package/package.json +2 -2
- package/dist/chunk-3YNNVSMC.js.map +0 -1
- package/dist/chunk-7FOAMNTI.cjs.map +0 -1
- package/dist/chunk-AYPF6C4D.cjs +0 -76
- package/dist/chunk-AYPF6C4D.cjs.map +0 -1
- package/dist/chunk-CLIH2T74.js.map +0 -1
- package/dist/chunk-EHSZ6TAN.cjs.map +0 -1
- package/dist/chunk-F332TENF.js.map +0 -1
- package/dist/chunk-GRN3GHUG.js.map +0 -1
- package/dist/chunk-J7F3NPAP.cjs.map +0 -1
- package/dist/chunk-L2IDZI7F.js.map +0 -1
- package/dist/chunk-UISZ2MBW.js.map +0 -1
- package/dist/chunk-V3KVSEG6.js +0 -74
- package/dist/chunk-V3KVSEG6.js.map +0 -1
- package/dist/chunk-Y72M3MVX.cjs.map +0 -1
- package/dist/chunk-YHW27CBV.cjs.map +0 -1
- package/dist/chunk-YRFAQ6MI.cjs.map +0 -1
- package/dist/migrations-LEMFV2ND.cjs +0 -13
- package/dist/migrations-RKQES6XY.js +0 -4
- package/migrations/025_rename_mdxeditor_to_easy_mdx.sql +0 -22
- /package/migrations/{029_ai_search_plugin.sql → 031_ai_search_plugin.sql} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
|
-
import { renderConfirmationDialog, getConfirmationDialogScript, api_default, api_media_default, api_system_default, admin_api_default, router, adminCollectionsRoutes, adminSettingsRoutes, admin_content_default, adminMediaRoutes, adminPluginRoutes, adminLogsRoutes, userRoutes, auth_default, test_cleanup_default } from './chunk-
|
|
2
|
-
export { ROUTES_INFO, admin_api_default as adminApiRoutes, adminCheckboxRoutes, admin_code_examples_default as adminCodeExamplesRoutes, adminCollectionsRoutes, admin_content_default as adminContentRoutes, router as adminDashboardRoutes, adminDesignRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_testimonials_default as adminTestimonialsRoutes, userRoutes as adminUsersRoutes, api_content_crud_default as apiContentCrudRoutes, api_media_default as apiMediaRoutes, api_default as apiRoutes, api_system_default as apiSystemRoutes, auth_default as authRoutes } from './chunk-
|
|
3
|
-
import { schema_exports } from './chunk-
|
|
4
|
-
export { Logger, apiTokens, collections, content, contentVersions, getLogger, initLogger, insertCollectionSchema, insertContentSchema, insertLogConfigSchema, insertMediaSchema, insertPluginActivityLogSchema, insertPluginAssetSchema, insertPluginHookSchema, insertPluginRouteSchema, insertPluginSchema, insertSystemLogSchema, insertUserSchema, insertWorkflowHistorySchema, logConfig, media, pluginActivityLog, pluginAssets, pluginHooks, pluginRoutes, plugins, selectCollectionSchema, selectContentSchema, selectLogConfigSchema, selectMediaSchema, selectPluginActivityLogSchema, selectPluginAssetSchema, selectPluginHookSchema, selectPluginRouteSchema, selectPluginSchema, selectSystemLogSchema, selectUserSchema, selectWorkflowHistorySchema, systemLogs, users, workflowHistory } from './chunk-
|
|
5
|
-
import { requireAuth, AuthManager, metricsMiddleware, bootstrapMiddleware } from './chunk-
|
|
6
|
-
export { AuthManager, PermissionManager, bootstrapMiddleware, cacheHeaders, compressionMiddleware, detailedLoggingMiddleware, getActivePlugins, isPluginActive, logActivity, loggingMiddleware, optionalAuth, performanceLoggingMiddleware, requireActivePlugin, requireActivePlugins, requireAnyPermission, requireAuth, requirePermission, requireRole, securityHeaders, securityLoggingMiddleware } from './chunk-
|
|
1
|
+
import { renderConfirmationDialog, getConfirmationDialogScript, api_default, api_media_default, api_system_default, admin_api_default, router, adminCollectionsRoutes, adminFormsRoutes, adminSettingsRoutes, public_forms_default, router2, admin_content_default, adminMediaRoutes, adminPluginRoutes, adminLogsRoutes, userRoutes, auth_default, test_cleanup_default } from './chunk-BAWMAS5S.js';
|
|
2
|
+
export { ROUTES_INFO, admin_api_default as adminApiRoutes, adminCheckboxRoutes, admin_code_examples_default as adminCodeExamplesRoutes, adminCollectionsRoutes, admin_content_default as adminContentRoutes, router as adminDashboardRoutes, adminDesignRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_testimonials_default as adminTestimonialsRoutes, userRoutes as adminUsersRoutes, api_content_crud_default as apiContentCrudRoutes, api_media_default as apiMediaRoutes, api_default as apiRoutes, api_system_default as apiSystemRoutes, auth_default as authRoutes } from './chunk-BAWMAS5S.js';
|
|
3
|
+
import { SettingsService, schema_exports } from './chunk-G44QUVNM.js';
|
|
4
|
+
export { Logger, apiTokens, collections, content, contentVersions, getLogger, initLogger, insertCollectionSchema, insertContentSchema, insertLogConfigSchema, insertMediaSchema, insertPluginActivityLogSchema, insertPluginAssetSchema, insertPluginHookSchema, insertPluginRouteSchema, insertPluginSchema, insertSystemLogSchema, insertUserSchema, insertWorkflowHistorySchema, logConfig, media, pluginActivityLog, pluginAssets, pluginHooks, pluginRoutes, plugins, selectCollectionSchema, selectContentSchema, selectLogConfigSchema, selectMediaSchema, selectPluginActivityLogSchema, selectPluginAssetSchema, selectPluginHookSchema, selectPluginRouteSchema, selectPluginSchema, selectSystemLogSchema, selectUserSchema, selectWorkflowHistorySchema, systemLogs, users, workflowHistory } from './chunk-G44QUVNM.js';
|
|
5
|
+
import { requireAuth, AuthManager, metricsMiddleware, bootstrapMiddleware } from './chunk-3E76TKR5.js';
|
|
6
|
+
export { AuthManager, PermissionManager, bootstrapMiddleware, cacheHeaders, compressionMiddleware, detailedLoggingMiddleware, getActivePlugins, isPluginActive, logActivity, loggingMiddleware, optionalAuth, performanceLoggingMiddleware, requireActivePlugin, requireActivePlugins, requireAnyPermission, requireAuth, requirePermission, requireRole, securityHeaders, securityLoggingMiddleware } from './chunk-3E76TKR5.js';
|
|
7
7
|
export { PluginBootstrapService, PluginService as PluginServiceClass, cleanupRemovedCollections, fullCollectionSync, getAvailableCollectionNames, getManagedCollections, isCollectionManaged, loadCollectionConfig, loadCollectionConfigs, registerCollections, syncCollection, syncCollections, validateCollectionConfig } from './chunk-YFJJU26H.js';
|
|
8
|
-
export { MigrationService } from './chunk-
|
|
9
|
-
export { renderFilterBar } from './chunk-
|
|
10
|
-
import { init_admin_layout_catalyst_template, renderAdminLayout,
|
|
11
|
-
export { getConfirmationDialogScript, renderAlert, renderConfirmationDialog, renderForm, renderFormField, renderPagination, renderTable } from './chunk-
|
|
12
|
-
export { HookSystemImpl, HookUtils, PluginManager as PluginManagerClass, PluginRegistryImpl, PluginValidator as PluginValidatorClass, ScopedHookSystem as ScopedHookSystemClass } from './chunk-
|
|
13
|
-
import { PluginBuilder } from './chunk-
|
|
14
|
-
export { PluginBuilder, PluginHelpers } from './chunk-
|
|
15
|
-
import { package_default, getCoreVersion } from './chunk-
|
|
16
|
-
export { QueryFilterBuilder, SONICJS_VERSION, TemplateRenderer, buildQuery, escapeHtml, getCoreVersion, renderTemplate, sanitizeInput, sanitizeObject, templateRenderer } from './chunk-
|
|
8
|
+
export { MigrationService } from './chunk-JDFPB6UW.js';
|
|
9
|
+
export { renderFilterBar } from './chunk-H7AMQWVI.js';
|
|
10
|
+
import { init_admin_layout_catalyst_template, renderAdminLayout, renderAdminLayoutCatalyst } from './chunk-VCH6HXVP.js';
|
|
11
|
+
export { getConfirmationDialogScript, renderAlert, renderConfirmationDialog, renderForm, renderFormField, renderPagination, renderTable } from './chunk-VCH6HXVP.js';
|
|
12
|
+
export { HookSystemImpl, HookUtils, PluginManager as PluginManagerClass, PluginRegistryImpl, PluginValidator as PluginValidatorClass, ScopedHookSystem as ScopedHookSystemClass } from './chunk-CJYFSKH7.js';
|
|
13
|
+
import { PluginBuilder } from './chunk-J5WGMRSU.js';
|
|
14
|
+
export { PluginBuilder, PluginHelpers } from './chunk-J5WGMRSU.js';
|
|
15
|
+
import { package_default, getCoreVersion } from './chunk-34QIAULP.js';
|
|
16
|
+
export { QueryFilterBuilder, SONICJS_VERSION, TemplateRenderer, buildQuery, escapeHtml, getCoreVersion, renderTemplate, sanitizeInput, sanitizeObject, templateRenderer } from './chunk-34QIAULP.js';
|
|
17
17
|
import './chunk-X7ZAEI5S.js';
|
|
18
18
|
export { metricsTracker } from './chunk-FICTAGD4.js';
|
|
19
19
|
export { HOOKS } from './chunk-LOUJRBXV.js';
|
|
20
20
|
import './chunk-V4OQ3NZ2.js';
|
|
21
21
|
import { Hono } from 'hono';
|
|
22
|
-
import { html } from 'hono/html';
|
|
23
22
|
import { setCookie } from 'hono/cookie';
|
|
24
23
|
import { z } from 'zod';
|
|
25
24
|
import { drizzle } from 'drizzle-orm/d1';
|
|
@@ -562,9 +561,9 @@ function formatCellValue(value) {
|
|
|
562
561
|
|
|
563
562
|
// src/plugins/core-plugins/database-tools-plugin/admin-routes.ts
|
|
564
563
|
function createDatabaseToolsAdminRoutes() {
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
|
|
564
|
+
const router3 = new Hono();
|
|
565
|
+
router3.use("*", requireAuth());
|
|
566
|
+
router3.get("/api/stats", async (c) => {
|
|
568
567
|
try {
|
|
569
568
|
const user = c.get("user");
|
|
570
569
|
if (!user || user.role !== "admin") {
|
|
@@ -588,7 +587,7 @@ function createDatabaseToolsAdminRoutes() {
|
|
|
588
587
|
}, 500);
|
|
589
588
|
}
|
|
590
589
|
});
|
|
591
|
-
|
|
590
|
+
router3.post("/api/truncate", async (c) => {
|
|
592
591
|
try {
|
|
593
592
|
const user = c.get("user");
|
|
594
593
|
if (!user || user.role !== "admin") {
|
|
@@ -625,7 +624,7 @@ function createDatabaseToolsAdminRoutes() {
|
|
|
625
624
|
}, 500);
|
|
626
625
|
}
|
|
627
626
|
});
|
|
628
|
-
|
|
627
|
+
router3.post("/api/backup", async (c) => {
|
|
629
628
|
try {
|
|
630
629
|
const user = c.get("user");
|
|
631
630
|
if (!user || user.role !== "admin") {
|
|
@@ -652,7 +651,7 @@ function createDatabaseToolsAdminRoutes() {
|
|
|
652
651
|
}, 500);
|
|
653
652
|
}
|
|
654
653
|
});
|
|
655
|
-
|
|
654
|
+
router3.get("/api/validate", async (c) => {
|
|
656
655
|
try {
|
|
657
656
|
const user = c.get("user");
|
|
658
657
|
if (!user || user.role !== "admin") {
|
|
@@ -676,7 +675,7 @@ function createDatabaseToolsAdminRoutes() {
|
|
|
676
675
|
}, 500);
|
|
677
676
|
}
|
|
678
677
|
});
|
|
679
|
-
|
|
678
|
+
router3.get("/api/tables/:tableName", async (c) => {
|
|
680
679
|
try {
|
|
681
680
|
const user = c.get("user");
|
|
682
681
|
if (!user || user.role !== "admin") {
|
|
@@ -705,7 +704,7 @@ function createDatabaseToolsAdminRoutes() {
|
|
|
705
704
|
}, 500);
|
|
706
705
|
}
|
|
707
706
|
});
|
|
708
|
-
|
|
707
|
+
router3.get("/tables/:tableName", async (c) => {
|
|
709
708
|
try {
|
|
710
709
|
const user = c.get("user");
|
|
711
710
|
if (!user || user.role !== "admin") {
|
|
@@ -741,7 +740,7 @@ function createDatabaseToolsAdminRoutes() {
|
|
|
741
740
|
return c.text(`Error: ${error}`, 500);
|
|
742
741
|
}
|
|
743
742
|
});
|
|
744
|
-
return
|
|
743
|
+
return router3;
|
|
745
744
|
}
|
|
746
745
|
|
|
747
746
|
// src/plugins/core-plugins/seed-data-plugin/services/seed-data-service.ts
|
|
@@ -1031,7 +1030,7 @@ var SeedDataService = class {
|
|
|
1031
1030
|
function createSeedDataAdminRoutes() {
|
|
1032
1031
|
const routes = new Hono();
|
|
1033
1032
|
routes.get("/", async (c) => {
|
|
1034
|
-
const
|
|
1033
|
+
const html = `
|
|
1035
1034
|
<!DOCTYPE html>
|
|
1036
1035
|
<html>
|
|
1037
1036
|
<head>
|
|
@@ -1274,7 +1273,7 @@ function createSeedDataAdminRoutes() {
|
|
|
1274
1273
|
</body>
|
|
1275
1274
|
</html>
|
|
1276
1275
|
`;
|
|
1277
|
-
return c.html(
|
|
1276
|
+
return c.html(html);
|
|
1278
1277
|
});
|
|
1279
1278
|
routes.post("/generate", async (c) => {
|
|
1280
1279
|
try {
|
|
@@ -1325,253 +1324,6 @@ function createEmailPlugin() {
|
|
|
1325
1324
|
compatibility: "^2.0.0"
|
|
1326
1325
|
});
|
|
1327
1326
|
const emailRoutes = new Hono();
|
|
1328
|
-
emailRoutes.get("/settings", async (c) => {
|
|
1329
|
-
const user = c.get("user");
|
|
1330
|
-
const db = c.env.DB;
|
|
1331
|
-
const plugin2 = await db.prepare(`
|
|
1332
|
-
SELECT settings FROM plugins WHERE id = 'email'
|
|
1333
|
-
`).first();
|
|
1334
|
-
const settings = plugin2?.settings ? JSON.parse(plugin2.settings) : {};
|
|
1335
|
-
const contentHTML = await html`
|
|
1336
|
-
<div class="p-8">
|
|
1337
|
-
<!-- Header -->
|
|
1338
|
-
<div class="mb-8">
|
|
1339
|
-
<h1 class="text-3xl font-bold text-zinc-950 dark:text-white mb-2">Email Settings</h1>
|
|
1340
|
-
<p class="text-zinc-600 dark:text-zinc-400">Configure Resend API for sending transactional emails</p>
|
|
1341
|
-
</div>
|
|
1342
|
-
|
|
1343
|
-
<!-- Settings Form -->
|
|
1344
|
-
<div class="max-w-3xl">
|
|
1345
|
-
<!-- Main Settings Card -->
|
|
1346
|
-
<div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 p-6 mb-6">
|
|
1347
|
-
<h2 class="text-xl font-semibold text-zinc-950 dark:text-white mb-4">Resend Configuration</h2>
|
|
1348
|
-
|
|
1349
|
-
<form id="emailSettingsForm" class="space-y-6">
|
|
1350
|
-
<!-- API Key -->
|
|
1351
|
-
<div>
|
|
1352
|
-
<label for="apiKey" class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">
|
|
1353
|
-
Resend API Key <span class="text-red-500">*</span>
|
|
1354
|
-
</label>
|
|
1355
|
-
<input
|
|
1356
|
-
type="password"
|
|
1357
|
-
id="apiKey"
|
|
1358
|
-
name="apiKey"
|
|
1359
|
-
value="${settings.apiKey || ""}"
|
|
1360
|
-
class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-500 dark:placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-500 dark:focus:ring-indigo-400"
|
|
1361
|
-
placeholder="re_..."
|
|
1362
|
-
required
|
|
1363
|
-
/>
|
|
1364
|
-
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">
|
|
1365
|
-
Get your API key from <a href="https://resend.com/api-keys" target="_blank" class="text-indigo-600 dark:text-indigo-400 hover:underline">resend.com/api-keys</a>
|
|
1366
|
-
</p>
|
|
1367
|
-
</div>
|
|
1368
|
-
|
|
1369
|
-
<!-- From Email -->
|
|
1370
|
-
<div>
|
|
1371
|
-
<label for="fromEmail" class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">
|
|
1372
|
-
From Email <span class="text-red-500">*</span>
|
|
1373
|
-
</label>
|
|
1374
|
-
<input
|
|
1375
|
-
type="email"
|
|
1376
|
-
id="fromEmail"
|
|
1377
|
-
name="fromEmail"
|
|
1378
|
-
value="${settings.fromEmail || ""}"
|
|
1379
|
-
class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-500 dark:placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-500 dark:focus:ring-indigo-400"
|
|
1380
|
-
placeholder="noreply@yourdomain.com"
|
|
1381
|
-
required
|
|
1382
|
-
/>
|
|
1383
|
-
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">
|
|
1384
|
-
Must be a verified domain in Resend
|
|
1385
|
-
</p>
|
|
1386
|
-
</div>
|
|
1387
|
-
|
|
1388
|
-
<!-- From Name -->
|
|
1389
|
-
<div>
|
|
1390
|
-
<label for="fromName" class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">
|
|
1391
|
-
From Name <span class="text-red-500">*</span>
|
|
1392
|
-
</label>
|
|
1393
|
-
<input
|
|
1394
|
-
type="text"
|
|
1395
|
-
id="fromName"
|
|
1396
|
-
name="fromName"
|
|
1397
|
-
value="${settings.fromName || ""}"
|
|
1398
|
-
class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-500 dark:placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-500 dark:focus:ring-indigo-400"
|
|
1399
|
-
placeholder="Your App Name"
|
|
1400
|
-
required
|
|
1401
|
-
/>
|
|
1402
|
-
</div>
|
|
1403
|
-
|
|
1404
|
-
<!-- Reply To -->
|
|
1405
|
-
<div>
|
|
1406
|
-
<label for="replyTo" class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">
|
|
1407
|
-
Reply-To Email
|
|
1408
|
-
</label>
|
|
1409
|
-
<input
|
|
1410
|
-
type="email"
|
|
1411
|
-
id="replyTo"
|
|
1412
|
-
name="replyTo"
|
|
1413
|
-
value="${settings.replyTo || ""}"
|
|
1414
|
-
class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-500 dark:placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-500 dark:focus:ring-indigo-400"
|
|
1415
|
-
placeholder="support@yourdomain.com"
|
|
1416
|
-
/>
|
|
1417
|
-
</div>
|
|
1418
|
-
|
|
1419
|
-
<!-- Logo URL -->
|
|
1420
|
-
<div>
|
|
1421
|
-
<label for="logoUrl" class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">
|
|
1422
|
-
Logo URL
|
|
1423
|
-
</label>
|
|
1424
|
-
<input
|
|
1425
|
-
type="url"
|
|
1426
|
-
id="logoUrl"
|
|
1427
|
-
name="logoUrl"
|
|
1428
|
-
value="${settings.logoUrl || ""}"
|
|
1429
|
-
class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-500 dark:placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-500 dark:focus:ring-indigo-400"
|
|
1430
|
-
placeholder="https://yourdomain.com/logo.png"
|
|
1431
|
-
/>
|
|
1432
|
-
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">
|
|
1433
|
-
Logo to display in email templates
|
|
1434
|
-
</p>
|
|
1435
|
-
</div>
|
|
1436
|
-
|
|
1437
|
-
<!-- Action Buttons -->
|
|
1438
|
-
<div class="flex gap-3 pt-4">
|
|
1439
|
-
<button
|
|
1440
|
-
type="submit"
|
|
1441
|
-
class="inline-flex items-center justify-center rounded-lg bg-zinc-950 dark:bg-white px-3.5 py-2.5 text-sm font-semibold text-white dark:text-zinc-950 hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors shadow-sm"
|
|
1442
|
-
>
|
|
1443
|
-
Save Settings
|
|
1444
|
-
</button>
|
|
1445
|
-
<button
|
|
1446
|
-
type="button"
|
|
1447
|
-
id="testEmailBtn"
|
|
1448
|
-
class="inline-flex items-center justify-center rounded-lg bg-white dark:bg-zinc-800 px-3.5 py-2.5 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors shadow-sm"
|
|
1449
|
-
>
|
|
1450
|
-
Send Test Email
|
|
1451
|
-
</button>
|
|
1452
|
-
<button
|
|
1453
|
-
type="button"
|
|
1454
|
-
id="resetBtn"
|
|
1455
|
-
class="inline-flex items-center justify-center rounded-lg bg-white dark:bg-zinc-800 px-3.5 py-2.5 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors shadow-sm"
|
|
1456
|
-
>
|
|
1457
|
-
Reset
|
|
1458
|
-
</button>
|
|
1459
|
-
</div>
|
|
1460
|
-
</form>
|
|
1461
|
-
</div>
|
|
1462
|
-
|
|
1463
|
-
<!-- Status Message -->
|
|
1464
|
-
<div id="statusMessage" class="hidden rounded-xl p-4 mb-6"></div>
|
|
1465
|
-
|
|
1466
|
-
<!-- Info Card -->
|
|
1467
|
-
<div class="rounded-xl bg-indigo-50 dark:bg-indigo-950/30 ring-1 ring-indigo-100 dark:ring-indigo-900/50 p-6">
|
|
1468
|
-
<h3 class="font-semibold text-indigo-900 dark:text-indigo-300 mb-3">
|
|
1469
|
-
📧 Email Templates Included
|
|
1470
|
-
</h3>
|
|
1471
|
-
<ul class="text-sm text-indigo-800 dark:text-indigo-200 space-y-2">
|
|
1472
|
-
<li>✓ Registration confirmation</li>
|
|
1473
|
-
<li>✓ Email verification</li>
|
|
1474
|
-
<li>✓ Password reset</li>
|
|
1475
|
-
<li>✓ One-time code (2FA)</li>
|
|
1476
|
-
</ul>
|
|
1477
|
-
<p class="text-xs text-indigo-700 dark:text-indigo-300 mt-4">
|
|
1478
|
-
Templates are code-based and can be customized by editing the plugin files.
|
|
1479
|
-
</p>
|
|
1480
|
-
</div>
|
|
1481
|
-
</div>
|
|
1482
|
-
</div>
|
|
1483
|
-
|
|
1484
|
-
<script>
|
|
1485
|
-
// Form submission handler
|
|
1486
|
-
document.getElementById('emailSettingsForm').addEventListener('submit', async (e) => {
|
|
1487
|
-
e.preventDefault()
|
|
1488
|
-
const formData = new FormData(e.target)
|
|
1489
|
-
const data = Object.fromEntries(formData.entries())
|
|
1490
|
-
|
|
1491
|
-
const statusEl = document.getElementById('statusMessage')
|
|
1492
|
-
|
|
1493
|
-
try {
|
|
1494
|
-
const response = await fetch('/admin/plugins/email/settings', {
|
|
1495
|
-
method: 'POST',
|
|
1496
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1497
|
-
body: JSON.stringify(data)
|
|
1498
|
-
})
|
|
1499
|
-
|
|
1500
|
-
if (response.ok) {
|
|
1501
|
-
statusEl.className = 'rounded-xl bg-green-50 dark:bg-green-950/30 ring-1 ring-green-100 dark:ring-green-900/50 p-4 mb-6 text-green-900 dark:text-green-200'
|
|
1502
|
-
statusEl.innerHTML = '✅ Settings saved successfully!'
|
|
1503
|
-
statusEl.classList.remove('hidden')
|
|
1504
|
-
setTimeout(() => statusEl.classList.add('hidden'), 3000)
|
|
1505
|
-
} else {
|
|
1506
|
-
throw new Error('Failed to save settings')
|
|
1507
|
-
}
|
|
1508
|
-
} catch (error) {
|
|
1509
|
-
statusEl.className = 'rounded-xl bg-red-50 dark:bg-red-950/30 ring-1 ring-red-100 dark:ring-red-900/50 p-4 mb-6 text-red-900 dark:text-red-200'
|
|
1510
|
-
statusEl.innerHTML = '❌ Failed to save settings. Please try again.'
|
|
1511
|
-
statusEl.classList.remove('hidden')
|
|
1512
|
-
}
|
|
1513
|
-
})
|
|
1514
|
-
|
|
1515
|
-
// Test email handler
|
|
1516
|
-
document.getElementById('testEmailBtn').addEventListener('click', async () => {
|
|
1517
|
-
// Prompt for destination email
|
|
1518
|
-
const toEmail = prompt('Enter destination email address for test:')
|
|
1519
|
-
if (!toEmail) return
|
|
1520
|
-
|
|
1521
|
-
// Basic email validation
|
|
1522
|
-
if (!toEmail.match(/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/)) {
|
|
1523
|
-
alert('Please enter a valid email address')
|
|
1524
|
-
return
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
const statusEl = document.getElementById('statusMessage')
|
|
1528
|
-
|
|
1529
|
-
statusEl.className = 'rounded-xl bg-indigo-50 dark:bg-indigo-950/30 ring-1 ring-indigo-100 dark:ring-indigo-900/50 p-4 mb-6 text-indigo-900 dark:text-indigo-200'
|
|
1530
|
-
statusEl.innerHTML = \`📧 Sending test email to \${toEmail}...\`
|
|
1531
|
-
statusEl.classList.remove('hidden')
|
|
1532
|
-
|
|
1533
|
-
try {
|
|
1534
|
-
const response = await fetch('/admin/plugins/email/test', {
|
|
1535
|
-
method: 'POST',
|
|
1536
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1537
|
-
body: JSON.stringify({ toEmail })
|
|
1538
|
-
})
|
|
1539
|
-
|
|
1540
|
-
const data = await response.json()
|
|
1541
|
-
|
|
1542
|
-
if (response.ok) {
|
|
1543
|
-
statusEl.className = 'rounded-xl bg-green-50 dark:bg-green-950/30 ring-1 ring-green-100 dark:ring-green-900/50 p-4 mb-6 text-green-900 dark:text-green-200'
|
|
1544
|
-
statusEl.innerHTML = \`✅ \${data.message || 'Test email sent! Check your inbox.'}\`
|
|
1545
|
-
} else {
|
|
1546
|
-
statusEl.className = 'rounded-xl bg-red-50 dark:bg-red-950/30 ring-1 ring-red-100 dark:ring-red-900/50 p-4 mb-6 text-red-900 dark:text-red-200'
|
|
1547
|
-
statusEl.innerHTML = \`❌ \${data.error || 'Failed to send test email. Check your settings.'}\`
|
|
1548
|
-
}
|
|
1549
|
-
} catch (error) {
|
|
1550
|
-
statusEl.className = 'rounded-xl bg-red-50 dark:bg-red-950/30 ring-1 ring-red-100 dark:ring-red-900/50 p-4 mb-6 text-red-900 dark:text-red-200'
|
|
1551
|
-
statusEl.innerHTML = '❌ Network error. Please try again.'
|
|
1552
|
-
}
|
|
1553
|
-
})
|
|
1554
|
-
|
|
1555
|
-
// Reset button handler
|
|
1556
|
-
document.getElementById('resetBtn').addEventListener('click', () => {
|
|
1557
|
-
document.getElementById('emailSettingsForm').reset()
|
|
1558
|
-
})
|
|
1559
|
-
</script>
|
|
1560
|
-
`;
|
|
1561
|
-
const templateUser = user ? {
|
|
1562
|
-
name: user.name ?? user.email ?? "Admin",
|
|
1563
|
-
email: user.email ?? "admin@sonicjs.com",
|
|
1564
|
-
role: user.role ?? "admin"
|
|
1565
|
-
} : void 0;
|
|
1566
|
-
return c.html(
|
|
1567
|
-
renderAdminLayout({
|
|
1568
|
-
title: "Email Settings",
|
|
1569
|
-
content: contentHTML,
|
|
1570
|
-
user: templateUser,
|
|
1571
|
-
currentPath: "/admin/plugins/email/settings"
|
|
1572
|
-
})
|
|
1573
|
-
);
|
|
1574
|
-
});
|
|
1575
1327
|
emailRoutes.post("/settings", async (c) => {
|
|
1576
1328
|
try {
|
|
1577
1329
|
const body = await c.req.json();
|
|
@@ -1667,7 +1419,7 @@ function createEmailPlugin() {
|
|
|
1667
1419
|
requiresAuth: true,
|
|
1668
1420
|
priority: 80
|
|
1669
1421
|
});
|
|
1670
|
-
builder.addMenuItem("Email", "/admin/plugins/email
|
|
1422
|
+
builder.addMenuItem("Email", "/admin/plugins/email", {
|
|
1671
1423
|
icon: "envelope",
|
|
1672
1424
|
order: 80,
|
|
1673
1425
|
permissions: ["email:manage"]
|
|
@@ -1984,8 +1736,7 @@ var DEFAULT_SETTINGS = {
|
|
|
1984
1736
|
codeExpiryMinutes: 10,
|
|
1985
1737
|
maxAttempts: 3,
|
|
1986
1738
|
rateLimitPerHour: 5,
|
|
1987
|
-
allowNewUserRegistration: false
|
|
1988
|
-
appName: "SonicJS"
|
|
1739
|
+
allowNewUserRegistration: false
|
|
1989
1740
|
};
|
|
1990
1741
|
function createOTPLoginPlugin() {
|
|
1991
1742
|
const builder = PluginBuilder.create({
|
|
@@ -2016,7 +1767,21 @@ function createOTPLoginPlugin() {
|
|
|
2016
1767
|
const normalizedEmail = email.toLowerCase();
|
|
2017
1768
|
const db = c.env.DB;
|
|
2018
1769
|
const otpService = new OTPService(db);
|
|
2019
|
-
|
|
1770
|
+
let settings = { ...DEFAULT_SETTINGS };
|
|
1771
|
+
const pluginRow = await db.prepare(`
|
|
1772
|
+
SELECT settings FROM plugins WHERE id = 'otp-login'
|
|
1773
|
+
`).first();
|
|
1774
|
+
if (pluginRow?.settings) {
|
|
1775
|
+
try {
|
|
1776
|
+
const savedSettings = JSON.parse(pluginRow.settings);
|
|
1777
|
+
settings = { ...DEFAULT_SETTINGS, ...savedSettings };
|
|
1778
|
+
} catch (e) {
|
|
1779
|
+
console.warn("Failed to parse OTP plugin settings, using defaults");
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
const settingsService = new SettingsService(db);
|
|
1783
|
+
const generalSettings = await settingsService.getGeneralSettings();
|
|
1784
|
+
const siteName = generalSettings.siteName;
|
|
2020
1785
|
const canRequest = await otpService.checkRateLimit(normalizedEmail, settings);
|
|
2021
1786
|
if (!canRequest) {
|
|
2022
1787
|
return c.json({
|
|
@@ -2060,7 +1825,7 @@ function createOTPLoginPlugin() {
|
|
|
2060
1825
|
email: normalizedEmail,
|
|
2061
1826
|
ipAddress,
|
|
2062
1827
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2063
|
-
appName:
|
|
1828
|
+
appName: siteName
|
|
2064
1829
|
});
|
|
2065
1830
|
const emailPlugin2 = await db.prepare(`
|
|
2066
1831
|
SELECT settings FROM plugins WHERE id = 'email'
|
|
@@ -2077,7 +1842,7 @@ function createOTPLoginPlugin() {
|
|
|
2077
1842
|
body: JSON.stringify({
|
|
2078
1843
|
from: `${emailSettings.fromName} <${emailSettings.fromEmail}>`,
|
|
2079
1844
|
to: [normalizedEmail],
|
|
2080
|
-
subject: `Your login code for ${
|
|
1845
|
+
subject: `Your login code for ${siteName}`,
|
|
2081
1846
|
html: emailContent.html,
|
|
2082
1847
|
text: emailContent.text,
|
|
2083
1848
|
reply_to: emailSettings.replyTo || emailSettings.fromEmail
|
|
@@ -2128,7 +1893,18 @@ function createOTPLoginPlugin() {
|
|
|
2128
1893
|
const normalizedEmail = email.toLowerCase();
|
|
2129
1894
|
const db = c.env.DB;
|
|
2130
1895
|
const otpService = new OTPService(db);
|
|
2131
|
-
|
|
1896
|
+
let settings = { ...DEFAULT_SETTINGS };
|
|
1897
|
+
const pluginRow = await db.prepare(`
|
|
1898
|
+
SELECT settings FROM plugins WHERE id = 'otp-login'
|
|
1899
|
+
`).first();
|
|
1900
|
+
if (pluginRow?.settings) {
|
|
1901
|
+
try {
|
|
1902
|
+
const savedSettings = JSON.parse(pluginRow.settings);
|
|
1903
|
+
settings = { ...DEFAULT_SETTINGS, ...savedSettings };
|
|
1904
|
+
} catch (e) {
|
|
1905
|
+
console.warn("Failed to parse OTP plugin settings, using defaults");
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
2132
1908
|
const verification = await otpService.verifyCode(normalizedEmail, code, settings);
|
|
2133
1909
|
if (!verification.valid) {
|
|
2134
1910
|
await otpService.incrementAttempts(normalizedEmail, code);
|
|
@@ -2207,193 +1983,7 @@ function createOTPLoginPlugin() {
|
|
|
2207
1983
|
requiresAuth: false,
|
|
2208
1984
|
priority: 100
|
|
2209
1985
|
});
|
|
2210
|
-
|
|
2211
|
-
adminRoutes2.get("/settings", async (c) => {
|
|
2212
|
-
const user = c.get("user");
|
|
2213
|
-
const contentHTML = await html`
|
|
2214
|
-
<div class="p-8">
|
|
2215
|
-
<div class="mb-8">
|
|
2216
|
-
<h1 class="text-3xl font-bold mb-2">OTP Login Settings</h1>
|
|
2217
|
-
<p class="text-zinc-600 dark:text-zinc-400">Configure passwordless authentication via email codes</p>
|
|
2218
|
-
</div>
|
|
2219
|
-
|
|
2220
|
-
<div class="max-w-3xl">
|
|
2221
|
-
<div class="backdrop-blur-md bg-black/20 border border-white/10 shadow-xl rounded-xl p-6 mb-6">
|
|
2222
|
-
<h2 class="text-xl font-semibold mb-4">Code Settings</h2>
|
|
2223
|
-
|
|
2224
|
-
<form id="otpSettingsForm" class="space-y-6">
|
|
2225
|
-
<div>
|
|
2226
|
-
<label for="codeLength" class="block text-sm font-medium mb-2">
|
|
2227
|
-
Code Length
|
|
2228
|
-
</label>
|
|
2229
|
-
<input
|
|
2230
|
-
type="number"
|
|
2231
|
-
id="codeLength"
|
|
2232
|
-
name="codeLength"
|
|
2233
|
-
min="4"
|
|
2234
|
-
max="8"
|
|
2235
|
-
value="6"
|
|
2236
|
-
class="w-full px-4 py-2 rounded-lg bg-white/5 border border-white/10 focus:border-blue-500 focus:outline-none"
|
|
2237
|
-
/>
|
|
2238
|
-
<p class="text-xs text-zinc-500 mt-1">Number of digits in OTP code (4-8)</p>
|
|
2239
|
-
</div>
|
|
2240
|
-
|
|
2241
|
-
<div>
|
|
2242
|
-
<label for="codeExpiryMinutes" class="block text-sm font-medium mb-2">
|
|
2243
|
-
Code Expiry (minutes)
|
|
2244
|
-
</label>
|
|
2245
|
-
<input
|
|
2246
|
-
type="number"
|
|
2247
|
-
id="codeExpiryMinutes"
|
|
2248
|
-
name="codeExpiryMinutes"
|
|
2249
|
-
min="5"
|
|
2250
|
-
max="60"
|
|
2251
|
-
value="10"
|
|
2252
|
-
class="w-full px-4 py-2 rounded-lg bg-white/5 border border-white/10 focus:border-blue-500 focus:outline-none"
|
|
2253
|
-
/>
|
|
2254
|
-
<p class="text-xs text-zinc-500 mt-1">How long codes remain valid (5-60 minutes)</p>
|
|
2255
|
-
</div>
|
|
2256
|
-
|
|
2257
|
-
<div>
|
|
2258
|
-
<label for="maxAttempts" class="block text-sm font-medium mb-2">
|
|
2259
|
-
Maximum Attempts
|
|
2260
|
-
</label>
|
|
2261
|
-
<input
|
|
2262
|
-
type="number"
|
|
2263
|
-
id="maxAttempts"
|
|
2264
|
-
name="maxAttempts"
|
|
2265
|
-
min="3"
|
|
2266
|
-
max="10"
|
|
2267
|
-
value="3"
|
|
2268
|
-
class="w-full px-4 py-2 rounded-lg bg-white/5 border border-white/10 focus:border-blue-500 focus:outline-none"
|
|
2269
|
-
/>
|
|
2270
|
-
<p class="text-xs text-zinc-500 mt-1">Max verification attempts before invalidation</p>
|
|
2271
|
-
</div>
|
|
2272
|
-
|
|
2273
|
-
<div>
|
|
2274
|
-
<label for="rateLimitPerHour" class="block text-sm font-medium mb-2">
|
|
2275
|
-
Rate Limit (per hour)
|
|
2276
|
-
</label>
|
|
2277
|
-
<input
|
|
2278
|
-
type="number"
|
|
2279
|
-
id="rateLimitPerHour"
|
|
2280
|
-
name="rateLimitPerHour"
|
|
2281
|
-
min="3"
|
|
2282
|
-
max="20"
|
|
2283
|
-
value="5"
|
|
2284
|
-
class="w-full px-4 py-2 rounded-lg bg-white/5 border border-white/10 focus:border-blue-500 focus:outline-none"
|
|
2285
|
-
/>
|
|
2286
|
-
<p class="text-xs text-zinc-500 mt-1">Max code requests per email per hour</p>
|
|
2287
|
-
</div>
|
|
2288
|
-
|
|
2289
|
-
<div class="flex items-center">
|
|
2290
|
-
<input
|
|
2291
|
-
type="checkbox"
|
|
2292
|
-
id="allowNewUserRegistration"
|
|
2293
|
-
name="allowNewUserRegistration"
|
|
2294
|
-
class="w-4 h-4 rounded border-white/10"
|
|
2295
|
-
/>
|
|
2296
|
-
<label for="allowNewUserRegistration" class="ml-2 text-sm">
|
|
2297
|
-
Allow new user registration via OTP
|
|
2298
|
-
</label>
|
|
2299
|
-
</div>
|
|
2300
|
-
|
|
2301
|
-
<div class="flex gap-3 pt-4">
|
|
2302
|
-
<button
|
|
2303
|
-
type="submit"
|
|
2304
|
-
class="px-6 py-2 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-lg font-medium hover:from-blue-600 hover:to-purple-700 transition-all"
|
|
2305
|
-
>
|
|
2306
|
-
Save Settings
|
|
2307
|
-
</button>
|
|
2308
|
-
<button
|
|
2309
|
-
type="button"
|
|
2310
|
-
id="testOTPBtn"
|
|
2311
|
-
class="px-6 py-2 bg-white/10 hover:bg-white/20 text-white rounded-lg font-medium transition-all"
|
|
2312
|
-
>
|
|
2313
|
-
Send Test Code
|
|
2314
|
-
</button>
|
|
2315
|
-
</div>
|
|
2316
|
-
</form>
|
|
2317
|
-
</div>
|
|
2318
|
-
|
|
2319
|
-
<div id="statusMessage" class="hidden backdrop-blur-md bg-black/20 border border-white/10 rounded-xl p-4 mb-6"></div>
|
|
2320
|
-
|
|
2321
|
-
<div class="backdrop-blur-md bg-blue-500/10 border border-blue-500/20 rounded-xl p-6">
|
|
2322
|
-
<h3 class="font-semibold text-blue-400 mb-3">
|
|
2323
|
-
🔢 Features
|
|
2324
|
-
</h3>
|
|
2325
|
-
<ul class="text-sm text-blue-200 space-y-2">
|
|
2326
|
-
<li>✓ Passwordless authentication</li>
|
|
2327
|
-
<li>✓ Secure random code generation</li>
|
|
2328
|
-
<li>✓ Rate limiting protection</li>
|
|
2329
|
-
<li>✓ Brute force prevention</li>
|
|
2330
|
-
<li>✓ Mobile-friendly UX</li>
|
|
2331
|
-
</ul>
|
|
2332
|
-
</div>
|
|
2333
|
-
</div>
|
|
2334
|
-
</div>
|
|
2335
|
-
|
|
2336
|
-
<script>
|
|
2337
|
-
document.getElementById('otpSettingsForm').addEventListener('submit', async (e) => {
|
|
2338
|
-
e.preventDefault()
|
|
2339
|
-
const statusEl = document.getElementById('statusMessage')
|
|
2340
|
-
statusEl.className = 'backdrop-blur-md bg-green-500/20 border border-green-500/30 rounded-xl p-4 mb-6'
|
|
2341
|
-
statusEl.innerHTML = '✅ Settings saved successfully!'
|
|
2342
|
-
statusEl.classList.remove('hidden')
|
|
2343
|
-
setTimeout(() => statusEl.classList.add('hidden'), 3000)
|
|
2344
|
-
})
|
|
2345
|
-
|
|
2346
|
-
document.getElementById('testOTPBtn').addEventListener('click', async () => {
|
|
2347
|
-
const email = prompt('Enter email address for test:')
|
|
2348
|
-
if (!email) return
|
|
2349
|
-
|
|
2350
|
-
const statusEl = document.getElementById('statusMessage')
|
|
2351
|
-
statusEl.className = 'backdrop-blur-md bg-blue-500/20 border border-blue-500/30 rounded-xl p-4 mb-6'
|
|
2352
|
-
statusEl.innerHTML = '📧 Sending test code...'
|
|
2353
|
-
statusEl.classList.remove('hidden')
|
|
2354
|
-
|
|
2355
|
-
try {
|
|
2356
|
-
const response = await fetch('/auth/otp/request', {
|
|
2357
|
-
method: 'POST',
|
|
2358
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2359
|
-
body: JSON.stringify({ email })
|
|
2360
|
-
})
|
|
2361
|
-
|
|
2362
|
-
const data = await response.json()
|
|
2363
|
-
|
|
2364
|
-
if (response.ok) {
|
|
2365
|
-
statusEl.className = 'backdrop-blur-md bg-green-500/20 border border-green-500/30 rounded-xl p-4 mb-6'
|
|
2366
|
-
statusEl.innerHTML = '✅ Test code sent!' + (data.dev_code ? \` Code: <strong>\${data.dev_code}</strong>\` : '')
|
|
2367
|
-
} else {
|
|
2368
|
-
throw new Error(data.error || 'Failed')
|
|
2369
|
-
}
|
|
2370
|
-
} catch (error) {
|
|
2371
|
-
statusEl.className = 'backdrop-blur-md bg-red-500/20 border border-red-500/30 rounded-xl p-4 mb-6'
|
|
2372
|
-
statusEl.innerHTML = '❌ Failed to send test code'
|
|
2373
|
-
}
|
|
2374
|
-
})
|
|
2375
|
-
</script>
|
|
2376
|
-
`;
|
|
2377
|
-
const templateUser = user ? {
|
|
2378
|
-
name: user.name ?? user.email ?? "Admin",
|
|
2379
|
-
email: user.email ?? "admin@sonicjs.com",
|
|
2380
|
-
role: user.role ?? "admin"
|
|
2381
|
-
} : void 0;
|
|
2382
|
-
return c.html(
|
|
2383
|
-
adminLayoutV2({
|
|
2384
|
-
title: "OTP Login Settings",
|
|
2385
|
-
content: contentHTML,
|
|
2386
|
-
user: templateUser,
|
|
2387
|
-
currentPath: "/admin/plugins/otp-login/settings"
|
|
2388
|
-
})
|
|
2389
|
-
);
|
|
2390
|
-
});
|
|
2391
|
-
builder.addRoute("/admin/plugins/otp-login", adminRoutes2, {
|
|
2392
|
-
description: "OTP login admin interface",
|
|
2393
|
-
requiresAuth: true,
|
|
2394
|
-
priority: 85
|
|
2395
|
-
});
|
|
2396
|
-
builder.addMenuItem("OTP Login", "/admin/plugins/otp-login/settings", {
|
|
1986
|
+
builder.addMenuItem("OTP Login", "/admin/plugins/otp-login", {
|
|
2397
1987
|
icon: "key",
|
|
2398
1988
|
order: 85,
|
|
2399
1989
|
permissions: ["otp:manage"]
|
|
@@ -6418,7 +6008,11 @@ function createSonicJSApp(config = {}) {
|
|
|
6418
6008
|
app2.route("/admin/api", admin_api_default);
|
|
6419
6009
|
app2.route("/admin/dashboard", router);
|
|
6420
6010
|
app2.route("/admin/collections", adminCollectionsRoutes);
|
|
6011
|
+
app2.route("/admin/forms", adminFormsRoutes);
|
|
6421
6012
|
app2.route("/admin/settings", adminSettingsRoutes);
|
|
6013
|
+
app2.route("/forms", public_forms_default);
|
|
6014
|
+
app2.route("/api/forms", public_forms_default);
|
|
6015
|
+
app2.route("/admin/api-reference", router2);
|
|
6422
6016
|
app2.route("/admin/database-tools", createDatabaseToolsAdminRoutes());
|
|
6423
6017
|
app2.route("/admin/seed-data", createSeedDataAdminRoutes());
|
|
6424
6018
|
app2.route("/admin/content", admin_content_default);
|
|
@@ -6429,6 +6023,11 @@ function createSonicJSApp(config = {}) {
|
|
|
6429
6023
|
}
|
|
6430
6024
|
}
|
|
6431
6025
|
app2.route("/admin/cache", cache_default.getRoutes());
|
|
6026
|
+
if (otpLoginPlugin.routes && otpLoginPlugin.routes.length > 0) {
|
|
6027
|
+
for (const route of otpLoginPlugin.routes) {
|
|
6028
|
+
app2.route(route.path, route.handler);
|
|
6029
|
+
}
|
|
6030
|
+
}
|
|
6432
6031
|
app2.route("/admin/plugins", adminPluginRoutes);
|
|
6433
6032
|
app2.route("/admin/logs", adminLogsRoutes);
|
|
6434
6033
|
app2.route("/admin", userRoutes);
|
|
@@ -6439,11 +6038,6 @@ function createSonicJSApp(config = {}) {
|
|
|
6439
6038
|
app2.route(route.path, route.handler);
|
|
6440
6039
|
}
|
|
6441
6040
|
}
|
|
6442
|
-
if (otpLoginPlugin.routes && otpLoginPlugin.routes.length > 0) {
|
|
6443
|
-
for (const route of otpLoginPlugin.routes) {
|
|
6444
|
-
app2.route(route.path, route.handler);
|
|
6445
|
-
}
|
|
6446
|
-
}
|
|
6447
6041
|
const magicLinkPlugin = createMagicLinkAuthPlugin();
|
|
6448
6042
|
if (magicLinkPlugin.routes && magicLinkPlugin.routes.length > 0) {
|
|
6449
6043
|
for (const route of magicLinkPlugin.routes) {
|