@digilogiclabs/platform-core 1.13.0 → 1.14.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/auth.mjs CHANGED
@@ -1450,6 +1450,209 @@ function createLazyRateLimitStore(getRedis, options = {}) {
1450
1450
  };
1451
1451
  }
1452
1452
 
1453
+ // src/auth/secure-handler.ts
1454
+ function defaultLogger2(_request, operation) {
1455
+ const prefix = `[${operation}]`;
1456
+ return {
1457
+ info: (msg, meta) => console.log(prefix, msg, meta ? JSON.stringify(meta) : ""),
1458
+ warn: (msg, meta) => console.warn(prefix, msg, meta ? JSON.stringify(meta) : ""),
1459
+ error: (msg, meta) => console.error(prefix, msg, meta ? JSON.stringify(meta) : "")
1460
+ };
1461
+ }
1462
+ function createSecureHandlerFactory(factoryConfig) {
1463
+ const isDev = factoryConfig.isDevelopment ?? process.env.NODE_ENV === "development";
1464
+ const classify = factoryConfig.classifyError ?? classifyError;
1465
+ const makeLogger = factoryConfig.createLogger ?? defaultLogger2;
1466
+ function getRequestId(request) {
1467
+ const headers = request.headers;
1468
+ return headers.get("x-request-id") || headers.get("x-correlation-id") || crypto.randomUUID();
1469
+ }
1470
+ function checkLegacyToken(request) {
1471
+ if (!factoryConfig.adminSecret) return false;
1472
+ const auth = request.headers.get("authorization");
1473
+ if (!auth?.startsWith("Bearer ")) return false;
1474
+ return constantTimeEqual(auth.slice(7).trim(), factoryConfig.adminSecret);
1475
+ }
1476
+ function createSecureHandler(routeConfig, handler) {
1477
+ return async (request, routeContext) => {
1478
+ const requestId = getRequestId(request);
1479
+ const operation = routeConfig.operation || `${request.method} ${new URL(request.url).pathname}`;
1480
+ const log = makeLogger(request, operation);
1481
+ const params = await routeContext.params;
1482
+ let session = null;
1483
+ let isAdmin = false;
1484
+ let isLegacyToken = false;
1485
+ try {
1486
+ if (routeConfig.requireAuth || routeConfig.requireAdmin || routeConfig.requireRoles?.length) {
1487
+ session = await factoryConfig.getSession();
1488
+ if (session?.user) {
1489
+ isAdmin = factoryConfig.isAdmin(session);
1490
+ } else if (routeConfig.allowLegacyToken && checkLegacyToken(request)) {
1491
+ isLegacyToken = true;
1492
+ isAdmin = true;
1493
+ } else {
1494
+ log.warn("Unauthorized access attempt");
1495
+ return Response.json(
1496
+ { error: "Unauthorized" },
1497
+ { status: 401, headers: { "X-Request-ID": requestId } }
1498
+ );
1499
+ }
1500
+ }
1501
+ if (routeConfig.requireAdmin && !isLegacyToken && !isAdmin) {
1502
+ log.warn("Admin access denied");
1503
+ return Response.json(
1504
+ { error: "Forbidden: Admin access required" },
1505
+ { status: 403, headers: { "X-Request-ID": requestId } }
1506
+ );
1507
+ }
1508
+ if (routeConfig.requireRoles?.length && !isLegacyToken) {
1509
+ const hasRole2 = factoryConfig.hasAnyRole ? factoryConfig.hasAnyRole(session, routeConfig.requireRoles) : routeConfig.requireRoles.some(
1510
+ (r) => session?.user?.roles?.includes(r)
1511
+ );
1512
+ if (!hasRole2) {
1513
+ log.warn("Role-based access denied");
1514
+ return Response.json(
1515
+ { error: "Forbidden: Insufficient permissions" },
1516
+ { status: 403, headers: { "X-Request-ID": requestId } }
1517
+ );
1518
+ }
1519
+ }
1520
+ if (!routeConfig.skipRateLimit && routeConfig.rateLimit && factoryConfig.rateLimiter) {
1521
+ const userId = session?.user?.id ?? void 0;
1522
+ const blocked = await factoryConfig.rateLimiter.enforce(
1523
+ request,
1524
+ operation,
1525
+ routeConfig.rateLimit,
1526
+ userId
1527
+ );
1528
+ if (blocked) return blocked;
1529
+ }
1530
+ let validated = {};
1531
+ if (routeConfig.validate) {
1532
+ let input;
1533
+ if (request.method === "GET") {
1534
+ const url = new URL(request.url);
1535
+ const qs = {};
1536
+ url.searchParams.forEach((v, k) => qs[k] = v);
1537
+ input = qs;
1538
+ } else {
1539
+ const ct = request.headers.get("content-type");
1540
+ input = ct?.includes("application/json") ? await request.json() : {};
1541
+ }
1542
+ const result = routeConfig.validate.safeParse(input);
1543
+ if (!result.success) {
1544
+ const fieldErrors = result.error?.flatten ? result.error.flatten().fieldErrors : void 0;
1545
+ return Response.json(
1546
+ { error: "Validation error", errors: fieldErrors },
1547
+ { status: 400, headers: { "X-Request-ID": requestId } }
1548
+ );
1549
+ }
1550
+ validated = result.data;
1551
+ }
1552
+ const ctx = {
1553
+ session,
1554
+ isLegacyToken,
1555
+ isAdmin,
1556
+ validated,
1557
+ logger: log,
1558
+ requestId,
1559
+ params
1560
+ };
1561
+ const response = await handler(request, ctx);
1562
+ response.headers.set("X-Request-ID", requestId);
1563
+ if (routeConfig.audit && factoryConfig.auditLog) {
1564
+ const actorId = isLegacyToken ? "admin_token" : session?.user?.id || "anonymous";
1565
+ await factoryConfig.auditLog({
1566
+ actor: {
1567
+ id: actorId,
1568
+ type: isLegacyToken ? "admin" : "user",
1569
+ email: session?.user?.email ?? void 0
1570
+ },
1571
+ action: routeConfig.audit,
1572
+ resource: routeConfig.auditResource ? { type: routeConfig.auditResource, id: "unknown" } : void 0,
1573
+ outcome: "success"
1574
+ }).catch(() => {
1575
+ });
1576
+ }
1577
+ return response;
1578
+ } catch (error) {
1579
+ log.error("Request handler error", {
1580
+ error: error instanceof Error ? error.message : String(error)
1581
+ });
1582
+ if (routeConfig.audit && factoryConfig.auditLog) {
1583
+ await factoryConfig.auditLog({
1584
+ actor: {
1585
+ id: session?.user?.id || "unknown",
1586
+ type: "user"
1587
+ },
1588
+ action: routeConfig.audit,
1589
+ outcome: "failure",
1590
+ reason: error instanceof Error ? error.message : "Unknown error"
1591
+ }).catch(() => {
1592
+ });
1593
+ }
1594
+ const { status, body } = classify(error, isDev);
1595
+ return Response.json(
1596
+ { error: body.error, code: body.code },
1597
+ { status, headers: { "X-Request-ID": requestId } }
1598
+ );
1599
+ }
1600
+ };
1601
+ }
1602
+ function withPublicApi(config, handler) {
1603
+ return createSecureHandler(
1604
+ {
1605
+ rateLimit: factoryConfig.rateLimiter?.publicDefault,
1606
+ ...config,
1607
+ requireAuth: false,
1608
+ requireAdmin: false
1609
+ },
1610
+ handler
1611
+ );
1612
+ }
1613
+ function withAuthenticatedApi(config, handler) {
1614
+ return createSecureHandler(
1615
+ {
1616
+ rateLimit: factoryConfig.rateLimiter?.authDefault,
1617
+ ...config,
1618
+ requireAuth: true,
1619
+ requireAdmin: false
1620
+ },
1621
+ handler
1622
+ );
1623
+ }
1624
+ function withAdminApi(config, handler) {
1625
+ return createSecureHandler(
1626
+ {
1627
+ rateLimit: factoryConfig.rateLimiter?.adminDefault,
1628
+ ...config,
1629
+ requireAuth: true,
1630
+ requireAdmin: true
1631
+ },
1632
+ handler
1633
+ );
1634
+ }
1635
+ function withLegacyAdminApi(config, handler) {
1636
+ return createSecureHandler(
1637
+ {
1638
+ rateLimit: factoryConfig.rateLimiter?.adminDefault,
1639
+ ...config,
1640
+ requireAuth: true,
1641
+ requireAdmin: true,
1642
+ allowLegacyToken: true
1643
+ },
1644
+ handler
1645
+ );
1646
+ }
1647
+ return {
1648
+ createSecureHandler,
1649
+ withPublicApi,
1650
+ withAuthenticatedApi,
1651
+ withAdminApi,
1652
+ withLegacyAdminApi
1653
+ };
1654
+ }
1655
+
1453
1656
  // src/env.ts
1454
1657
  function getRequiredEnv(key) {
1455
1658
  const value = process.env[key];
@@ -1585,6 +1788,7 @@ export {
1585
1788
  createMemoryRateLimitStore,
1586
1789
  createRedisRateLimitStore,
1587
1790
  createSafeTextSchema,
1791
+ createSecureHandlerFactory,
1588
1792
  detectStage,
1589
1793
  enforceRateLimit,
1590
1794
  errorResponse,