@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.d.mts +186 -1
- package/dist/auth.d.ts +186 -1
- package/dist/auth.js +205 -0
- package/dist/auth.js.map +1 -1
- package/dist/auth.mjs +204 -0
- package/dist/auth.mjs.map +1 -1
- package/package.json +1 -1
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,
|