@digilogiclabs/platform-core 1.6.0 → 1.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/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,170 @@ 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
+
1109
+ // src/auth/beta-client.ts
1110
+ var DEFAULT_CONFIG = {
1111
+ baseUrl: "",
1112
+ settingsEndpoint: "/api/beta-settings",
1113
+ validateEndpoint: "/api/validate-beta-code",
1114
+ storageKey: "beta_code",
1115
+ failSafeDefaults: {
1116
+ betaMode: true,
1117
+ requireInviteCode: true,
1118
+ betaMessage: ""
1119
+ }
1120
+ };
1121
+ function createBetaClient(config = {}) {
1122
+ const cfg = {
1123
+ ...DEFAULT_CONFIG,
1124
+ ...config,
1125
+ failSafeDefaults: {
1126
+ ...DEFAULT_CONFIG.failSafeDefaults,
1127
+ ...config.failSafeDefaults
1128
+ }
1129
+ };
1130
+ return {
1131
+ fetchSettings: () => fetchBetaSettings(cfg),
1132
+ validateCode: (code) => validateBetaCode(code, cfg),
1133
+ storeCode: (code) => storeBetaCode(code, cfg),
1134
+ getStoredCode: () => getStoredBetaCode(cfg),
1135
+ clearStoredCode: () => clearStoredBetaCode(cfg)
1136
+ };
1137
+ }
1138
+ async function fetchBetaSettings(config = {}) {
1139
+ const cfg = { ...DEFAULT_CONFIG, ...config };
1140
+ try {
1141
+ const response = await fetch(
1142
+ `${cfg.baseUrl}${cfg.settingsEndpoint}`,
1143
+ {
1144
+ method: "GET",
1145
+ headers: { "Content-Type": "application/json" },
1146
+ cache: "no-store"
1147
+ }
1148
+ );
1149
+ if (!response.ok) {
1150
+ throw new Error(`Failed to fetch beta settings: ${response.status}`);
1151
+ }
1152
+ const data = await response.json();
1153
+ return {
1154
+ betaMode: data.betaMode ?? cfg.failSafeDefaults.betaMode ?? true,
1155
+ requireInviteCode: data.requireInviteCode ?? cfg.failSafeDefaults.requireInviteCode ?? true,
1156
+ betaMessage: data.betaMessage ?? cfg.failSafeDefaults.betaMessage ?? ""
1157
+ };
1158
+ } catch (error) {
1159
+ console.error("Error fetching beta settings:", error);
1160
+ return {
1161
+ betaMode: cfg.failSafeDefaults.betaMode ?? true,
1162
+ requireInviteCode: cfg.failSafeDefaults.requireInviteCode ?? true,
1163
+ betaMessage: cfg.failSafeDefaults.betaMessage ?? ""
1164
+ };
1165
+ }
1166
+ }
1167
+ async function validateBetaCode(code, config = {}) {
1168
+ const cfg = { ...DEFAULT_CONFIG, ...config };
1169
+ if (!code || code.trim().length < 3) {
1170
+ return {
1171
+ valid: false,
1172
+ message: "Please enter a valid invite code."
1173
+ };
1174
+ }
1175
+ try {
1176
+ const response = await fetch(
1177
+ `${cfg.baseUrl}${cfg.validateEndpoint}`,
1178
+ {
1179
+ method: "POST",
1180
+ headers: { "Content-Type": "application/json" },
1181
+ body: JSON.stringify({ code: code.trim().toUpperCase() })
1182
+ }
1183
+ );
1184
+ if (response.status === 429) {
1185
+ return {
1186
+ valid: false,
1187
+ message: "Too many attempts. Please try again later."
1188
+ };
1189
+ }
1190
+ if (!response.ok) {
1191
+ throw new Error(`Validation request failed: ${response.status}`);
1192
+ }
1193
+ return await response.json();
1194
+ } catch (error) {
1195
+ console.error("Error validating invite code:", error);
1196
+ return {
1197
+ valid: false,
1198
+ message: "Unable to validate code. Please try again."
1199
+ };
1200
+ }
1201
+ }
1202
+ function storeBetaCode(code, config = {}) {
1203
+ const key = config.storageKey ?? DEFAULT_CONFIG.storageKey;
1204
+ if (typeof window !== "undefined") {
1205
+ sessionStorage.setItem(key, code.trim().toUpperCase());
1206
+ }
1207
+ }
1208
+ function getStoredBetaCode(config = {}) {
1209
+ const key = config.storageKey ?? DEFAULT_CONFIG.storageKey;
1210
+ if (typeof window !== "undefined") {
1211
+ return sessionStorage.getItem(key);
1212
+ }
1213
+ return null;
1214
+ }
1215
+ function clearStoredBetaCode(config = {}) {
1216
+ const key = config.storageKey ?? DEFAULT_CONFIG.storageKey;
1217
+ if (typeof window !== "undefined") {
1218
+ sessionStorage.removeItem(key);
1219
+ }
1220
+ }
1221
+
1016
1222
  // src/env.ts
1017
1223
  function getRequiredEnv(key) {
1018
1224
  const value = process.env[key];
@@ -1134,20 +1340,27 @@ export {
1134
1340
  checkEnvVars,
1135
1341
  checkRateLimit,
1136
1342
  classifyError,
1343
+ clearStoredBetaCode,
1137
1344
  constantTimeEqual,
1138
1345
  containsHtml,
1139
1346
  containsUrls,
1140
1347
  createAuditActor,
1141
1348
  createAuditLogger,
1349
+ createBetaClient,
1142
1350
  createFeatureFlags,
1143
1351
  createMemoryRateLimitStore,
1352
+ createRedisRateLimitStore,
1144
1353
  createSafeTextSchema,
1145
1354
  detectStage,
1355
+ enforceRateLimit,
1356
+ errorResponse,
1146
1357
  escapeHtml,
1147
1358
  extractAuditIp,
1148
1359
  extractAuditRequestId,
1149
1360
  extractAuditUserAgent,
1361
+ extractBearerToken,
1150
1362
  extractClientIp,
1363
+ fetchBetaSettings,
1151
1364
  getBoolEnv,
1152
1365
  getCorrelationId,
1153
1366
  getEndSessionEndpoint,
@@ -1156,6 +1369,7 @@ export {
1156
1369
  getOptionalEnv,
1157
1370
  getRateLimitStatus,
1158
1371
  getRequiredEnv,
1372
+ getStoredBetaCode,
1159
1373
  getTokenEndpoint,
1160
1374
  hasAllRoles,
1161
1375
  hasAnyRole,
@@ -1163,13 +1377,17 @@ export {
1163
1377
  isAllowlisted,
1164
1378
  isApiError,
1165
1379
  isTokenExpired,
1380
+ isValidBearerToken,
1166
1381
  parseKeycloakRoles,
1167
1382
  refreshKeycloakToken,
1168
1383
  resetRateLimitForKey,
1169
1384
  resolveIdentifier,
1170
1385
  resolveRateLimitIdentifier,
1171
1386
  sanitizeApiError,
1387
+ storeBetaCode,
1172
1388
  stripHtml,
1173
- validateEnvVars
1389
+ validateBetaCode,
1390
+ validateEnvVars,
1391
+ zodErrorResponse
1174
1392
  };
1175
1393
  //# sourceMappingURL=auth.mjs.map