@digilogiclabs/platform-core 1.6.0 → 1.7.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
@@ -168,7 +168,11 @@ function buildKeycloakCallbacks(config) {
168
168
  * Compatible with Auth.js v5 JWT callback signature.
169
169
  */
170
170
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
- async jwt({ token, user, account }) {
171
+ async jwt({
172
+ token,
173
+ user,
174
+ account
175
+ }) {
172
176
  if (user) {
173
177
  token.id = token.sub ?? user.id;
174
178
  }
@@ -729,6 +733,44 @@ function resolveIdentifier(session, clientIp) {
729
733
  return { identifier: `ip:${clientIp ?? "unknown"}`, isAuthenticated: false };
730
734
  }
731
735
 
736
+ // src/auth/rate-limit-store-redis.ts
737
+ function createRedisRateLimitStore(redis, options = {}) {
738
+ const prefix = options.keyPrefix ?? "";
739
+ return {
740
+ async increment(key, windowMs, now) {
741
+ const fullKey = `${prefix}${key}`;
742
+ const windowStart = now - windowMs;
743
+ const windowSeconds = Math.ceil(windowMs / 1e3) + 60;
744
+ await redis.zremrangebyscore(fullKey, 0, windowStart);
745
+ const current = await redis.zcard(fullKey);
746
+ const member = `${now}:${Math.random().toString(36).slice(2, 10)}`;
747
+ await redis.zadd(fullKey, now, member);
748
+ await redis.expire(fullKey, windowSeconds);
749
+ return { count: current + 1 };
750
+ },
751
+ async isBlocked(key) {
752
+ const fullKey = `${prefix}${key}`;
753
+ const value = await redis.get(fullKey);
754
+ if (!value) {
755
+ return { blocked: false, ttlMs: 0 };
756
+ }
757
+ const ttlSeconds = await redis.ttl(fullKey);
758
+ if (ttlSeconds <= 0) {
759
+ return { blocked: false, ttlMs: 0 };
760
+ }
761
+ return { blocked: true, ttlMs: ttlSeconds * 1e3 };
762
+ },
763
+ async setBlock(key, durationSeconds) {
764
+ const fullKey = `${prefix}${key}`;
765
+ await redis.setex(fullKey, durationSeconds, "1");
766
+ },
767
+ async reset(key) {
768
+ const fullKey = `${prefix}${key}`;
769
+ await redis.del(fullKey);
770
+ }
771
+ };
772
+ }
773
+
732
774
  // src/auth/audit.ts
733
775
  var StandardAuditActions = {
734
776
  // Authentication
@@ -1013,6 +1055,57 @@ function buildPagination(page, limit, total) {
1013
1055
  };
1014
1056
  }
1015
1057
 
1058
+ // src/auth/nextjs-api.ts
1059
+ async function enforceRateLimit(request, operation, rule, options) {
1060
+ const identifier = options?.identifier ?? (options?.userId ? `user:${options.userId}` : void 0) ?? `ip:${extractClientIp((name) => request.headers.get(name))}`;
1061
+ const isAuthenticated = !!options?.userId;
1062
+ const result = await checkRateLimit(operation, identifier, rule, {
1063
+ ...options?.rateLimitOptions,
1064
+ isAuthenticated
1065
+ });
1066
+ if (!result.allowed) {
1067
+ const headers = buildRateLimitResponseHeaders(result);
1068
+ return new Response(
1069
+ JSON.stringify({
1070
+ error: "Rate limit exceeded. Please try again later.",
1071
+ retryAfter: result.retryAfterSeconds
1072
+ }),
1073
+ {
1074
+ status: 429,
1075
+ headers: { "Content-Type": "application/json", ...headers }
1076
+ }
1077
+ );
1078
+ }
1079
+ return null;
1080
+ }
1081
+ function errorResponse(error, options) {
1082
+ const isDev = options?.isDevelopment ?? process.env.NODE_ENV === "development";
1083
+ const { status, body } = classifyError(error, isDev);
1084
+ return new Response(JSON.stringify(body), {
1085
+ status,
1086
+ headers: { "Content-Type": "application/json" }
1087
+ });
1088
+ }
1089
+ function zodErrorResponse(error) {
1090
+ const firstIssue = error.issues[0];
1091
+ const message = firstIssue ? `${firstIssue.path.join(".") || "input"}: ${firstIssue.message}` : "Validation error";
1092
+ return new Response(JSON.stringify({ error: message }), {
1093
+ status: 400,
1094
+ headers: { "Content-Type": "application/json" }
1095
+ });
1096
+ }
1097
+ function extractBearerToken(request) {
1098
+ const auth = request.headers.get("authorization");
1099
+ if (!auth?.startsWith("Bearer ")) return null;
1100
+ return auth.slice(7).trim() || null;
1101
+ }
1102
+ function isValidBearerToken(request, secret) {
1103
+ if (!secret) return false;
1104
+ const token = extractBearerToken(request);
1105
+ if (!token) return false;
1106
+ return constantTimeEqual(token, secret);
1107
+ }
1108
+
1016
1109
  // src/env.ts
1017
1110
  function getRequiredEnv(key) {
1018
1111
  const value = process.env[key];
@@ -1141,12 +1234,16 @@ export {
1141
1234
  createAuditLogger,
1142
1235
  createFeatureFlags,
1143
1236
  createMemoryRateLimitStore,
1237
+ createRedisRateLimitStore,
1144
1238
  createSafeTextSchema,
1145
1239
  detectStage,
1240
+ enforceRateLimit,
1241
+ errorResponse,
1146
1242
  escapeHtml,
1147
1243
  extractAuditIp,
1148
1244
  extractAuditRequestId,
1149
1245
  extractAuditUserAgent,
1246
+ extractBearerToken,
1150
1247
  extractClientIp,
1151
1248
  getBoolEnv,
1152
1249
  getCorrelationId,
@@ -1163,6 +1260,7 @@ export {
1163
1260
  isAllowlisted,
1164
1261
  isApiError,
1165
1262
  isTokenExpired,
1263
+ isValidBearerToken,
1166
1264
  parseKeycloakRoles,
1167
1265
  refreshKeycloakToken,
1168
1266
  resetRateLimitForKey,
@@ -1170,6 +1268,7 @@ export {
1170
1268
  resolveRateLimitIdentifier,
1171
1269
  sanitizeApiError,
1172
1270
  stripHtml,
1173
- validateEnvVars
1271
+ validateEnvVars,
1272
+ zodErrorResponse
1174
1273
  };
1175
1274
  //# sourceMappingURL=auth.mjs.map