@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.js CHANGED
@@ -50,20 +50,27 @@ __export(auth_exports, {
50
50
  checkEnvVars: () => checkEnvVars,
51
51
  checkRateLimit: () => checkRateLimit,
52
52
  classifyError: () => classifyError,
53
+ clearStoredBetaCode: () => clearStoredBetaCode,
53
54
  constantTimeEqual: () => constantTimeEqual,
54
55
  containsHtml: () => containsHtml,
55
56
  containsUrls: () => containsUrls,
56
57
  createAuditActor: () => createAuditActor,
57
58
  createAuditLogger: () => createAuditLogger,
59
+ createBetaClient: () => createBetaClient,
58
60
  createFeatureFlags: () => createFeatureFlags,
59
61
  createMemoryRateLimitStore: () => createMemoryRateLimitStore,
62
+ createRedisRateLimitStore: () => createRedisRateLimitStore,
60
63
  createSafeTextSchema: () => createSafeTextSchema,
61
64
  detectStage: () => detectStage,
65
+ enforceRateLimit: () => enforceRateLimit,
66
+ errorResponse: () => errorResponse,
62
67
  escapeHtml: () => escapeHtml,
63
68
  extractAuditIp: () => extractAuditIp,
64
69
  extractAuditRequestId: () => extractAuditRequestId,
65
70
  extractAuditUserAgent: () => extractAuditUserAgent,
71
+ extractBearerToken: () => extractBearerToken,
66
72
  extractClientIp: () => extractClientIp,
73
+ fetchBetaSettings: () => fetchBetaSettings,
67
74
  getBoolEnv: () => getBoolEnv,
68
75
  getCorrelationId: () => getCorrelationId,
69
76
  getEndSessionEndpoint: () => getEndSessionEndpoint,
@@ -72,6 +79,7 @@ __export(auth_exports, {
72
79
  getOptionalEnv: () => getOptionalEnv,
73
80
  getRateLimitStatus: () => getRateLimitStatus,
74
81
  getRequiredEnv: () => getRequiredEnv,
82
+ getStoredBetaCode: () => getStoredBetaCode,
75
83
  getTokenEndpoint: () => getTokenEndpoint,
76
84
  hasAllRoles: () => hasAllRoles,
77
85
  hasAnyRole: () => hasAnyRole,
@@ -79,14 +87,18 @@ __export(auth_exports, {
79
87
  isAllowlisted: () => isAllowlisted,
80
88
  isApiError: () => isApiError,
81
89
  isTokenExpired: () => isTokenExpired,
90
+ isValidBearerToken: () => isValidBearerToken,
82
91
  parseKeycloakRoles: () => parseKeycloakRoles,
83
92
  refreshKeycloakToken: () => refreshKeycloakToken,
84
93
  resetRateLimitForKey: () => resetRateLimitForKey,
85
94
  resolveIdentifier: () => resolveIdentifier,
86
95
  resolveRateLimitIdentifier: () => resolveRateLimitIdentifier,
87
96
  sanitizeApiError: () => sanitizeApiError,
97
+ storeBetaCode: () => storeBetaCode,
88
98
  stripHtml: () => stripHtml,
89
- validateEnvVars: () => validateEnvVars
99
+ validateBetaCode: () => validateBetaCode,
100
+ validateEnvVars: () => validateEnvVars,
101
+ zodErrorResponse: () => zodErrorResponse
90
102
  });
91
103
  module.exports = __toCommonJS(auth_exports);
92
104
 
@@ -260,7 +272,11 @@ function buildKeycloakCallbacks(config) {
260
272
  * Compatible with Auth.js v5 JWT callback signature.
261
273
  */
262
274
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
263
- async jwt({ token, user, account }) {
275
+ async jwt({
276
+ token,
277
+ user,
278
+ account
279
+ }) {
264
280
  if (user) {
265
281
  token.id = token.sub ?? user.id;
266
282
  }
@@ -821,6 +837,44 @@ function resolveIdentifier(session, clientIp) {
821
837
  return { identifier: `ip:${clientIp ?? "unknown"}`, isAuthenticated: false };
822
838
  }
823
839
 
840
+ // src/auth/rate-limit-store-redis.ts
841
+ function createRedisRateLimitStore(redis, options = {}) {
842
+ const prefix = options.keyPrefix ?? "";
843
+ return {
844
+ async increment(key, windowMs, now) {
845
+ const fullKey = `${prefix}${key}`;
846
+ const windowStart = now - windowMs;
847
+ const windowSeconds = Math.ceil(windowMs / 1e3) + 60;
848
+ await redis.zremrangebyscore(fullKey, 0, windowStart);
849
+ const current = await redis.zcard(fullKey);
850
+ const member = `${now}:${Math.random().toString(36).slice(2, 10)}`;
851
+ await redis.zadd(fullKey, now, member);
852
+ await redis.expire(fullKey, windowSeconds);
853
+ return { count: current + 1 };
854
+ },
855
+ async isBlocked(key) {
856
+ const fullKey = `${prefix}${key}`;
857
+ const value = await redis.get(fullKey);
858
+ if (!value) {
859
+ return { blocked: false, ttlMs: 0 };
860
+ }
861
+ const ttlSeconds = await redis.ttl(fullKey);
862
+ if (ttlSeconds <= 0) {
863
+ return { blocked: false, ttlMs: 0 };
864
+ }
865
+ return { blocked: true, ttlMs: ttlSeconds * 1e3 };
866
+ },
867
+ async setBlock(key, durationSeconds) {
868
+ const fullKey = `${prefix}${key}`;
869
+ await redis.setex(fullKey, durationSeconds, "1");
870
+ },
871
+ async reset(key) {
872
+ const fullKey = `${prefix}${key}`;
873
+ await redis.del(fullKey);
874
+ }
875
+ };
876
+ }
877
+
824
878
  // src/auth/audit.ts
825
879
  var StandardAuditActions = {
826
880
  // Authentication
@@ -1105,6 +1159,170 @@ function buildPagination(page, limit, total) {
1105
1159
  };
1106
1160
  }
1107
1161
 
1162
+ // src/auth/nextjs-api.ts
1163
+ async function enforceRateLimit(request, operation, rule, options) {
1164
+ const identifier = options?.identifier ?? (options?.userId ? `user:${options.userId}` : void 0) ?? `ip:${extractClientIp((name) => request.headers.get(name))}`;
1165
+ const isAuthenticated = !!options?.userId;
1166
+ const result = await checkRateLimit(operation, identifier, rule, {
1167
+ ...options?.rateLimitOptions,
1168
+ isAuthenticated
1169
+ });
1170
+ if (!result.allowed) {
1171
+ const headers = buildRateLimitResponseHeaders(result);
1172
+ return new Response(
1173
+ JSON.stringify({
1174
+ error: "Rate limit exceeded. Please try again later.",
1175
+ retryAfter: result.retryAfterSeconds
1176
+ }),
1177
+ {
1178
+ status: 429,
1179
+ headers: { "Content-Type": "application/json", ...headers }
1180
+ }
1181
+ );
1182
+ }
1183
+ return null;
1184
+ }
1185
+ function errorResponse(error, options) {
1186
+ const isDev = options?.isDevelopment ?? process.env.NODE_ENV === "development";
1187
+ const { status, body } = classifyError(error, isDev);
1188
+ return new Response(JSON.stringify(body), {
1189
+ status,
1190
+ headers: { "Content-Type": "application/json" }
1191
+ });
1192
+ }
1193
+ function zodErrorResponse(error) {
1194
+ const firstIssue = error.issues[0];
1195
+ const message = firstIssue ? `${firstIssue.path.join(".") || "input"}: ${firstIssue.message}` : "Validation error";
1196
+ return new Response(JSON.stringify({ error: message }), {
1197
+ status: 400,
1198
+ headers: { "Content-Type": "application/json" }
1199
+ });
1200
+ }
1201
+ function extractBearerToken(request) {
1202
+ const auth = request.headers.get("authorization");
1203
+ if (!auth?.startsWith("Bearer ")) return null;
1204
+ return auth.slice(7).trim() || null;
1205
+ }
1206
+ function isValidBearerToken(request, secret) {
1207
+ if (!secret) return false;
1208
+ const token = extractBearerToken(request);
1209
+ if (!token) return false;
1210
+ return constantTimeEqual(token, secret);
1211
+ }
1212
+
1213
+ // src/auth/beta-client.ts
1214
+ var DEFAULT_CONFIG = {
1215
+ baseUrl: "",
1216
+ settingsEndpoint: "/api/beta-settings",
1217
+ validateEndpoint: "/api/validate-beta-code",
1218
+ storageKey: "beta_code",
1219
+ failSafeDefaults: {
1220
+ betaMode: true,
1221
+ requireInviteCode: true,
1222
+ betaMessage: ""
1223
+ }
1224
+ };
1225
+ function createBetaClient(config = {}) {
1226
+ const cfg = {
1227
+ ...DEFAULT_CONFIG,
1228
+ ...config,
1229
+ failSafeDefaults: {
1230
+ ...DEFAULT_CONFIG.failSafeDefaults,
1231
+ ...config.failSafeDefaults
1232
+ }
1233
+ };
1234
+ return {
1235
+ fetchSettings: () => fetchBetaSettings(cfg),
1236
+ validateCode: (code) => validateBetaCode(code, cfg),
1237
+ storeCode: (code) => storeBetaCode(code, cfg),
1238
+ getStoredCode: () => getStoredBetaCode(cfg),
1239
+ clearStoredCode: () => clearStoredBetaCode(cfg)
1240
+ };
1241
+ }
1242
+ async function fetchBetaSettings(config = {}) {
1243
+ const cfg = { ...DEFAULT_CONFIG, ...config };
1244
+ try {
1245
+ const response = await fetch(
1246
+ `${cfg.baseUrl}${cfg.settingsEndpoint}`,
1247
+ {
1248
+ method: "GET",
1249
+ headers: { "Content-Type": "application/json" },
1250
+ cache: "no-store"
1251
+ }
1252
+ );
1253
+ if (!response.ok) {
1254
+ throw new Error(`Failed to fetch beta settings: ${response.status}`);
1255
+ }
1256
+ const data = await response.json();
1257
+ return {
1258
+ betaMode: data.betaMode ?? cfg.failSafeDefaults.betaMode ?? true,
1259
+ requireInviteCode: data.requireInviteCode ?? cfg.failSafeDefaults.requireInviteCode ?? true,
1260
+ betaMessage: data.betaMessage ?? cfg.failSafeDefaults.betaMessage ?? ""
1261
+ };
1262
+ } catch (error) {
1263
+ console.error("Error fetching beta settings:", error);
1264
+ return {
1265
+ betaMode: cfg.failSafeDefaults.betaMode ?? true,
1266
+ requireInviteCode: cfg.failSafeDefaults.requireInviteCode ?? true,
1267
+ betaMessage: cfg.failSafeDefaults.betaMessage ?? ""
1268
+ };
1269
+ }
1270
+ }
1271
+ async function validateBetaCode(code, config = {}) {
1272
+ const cfg = { ...DEFAULT_CONFIG, ...config };
1273
+ if (!code || code.trim().length < 3) {
1274
+ return {
1275
+ valid: false,
1276
+ message: "Please enter a valid invite code."
1277
+ };
1278
+ }
1279
+ try {
1280
+ const response = await fetch(
1281
+ `${cfg.baseUrl}${cfg.validateEndpoint}`,
1282
+ {
1283
+ method: "POST",
1284
+ headers: { "Content-Type": "application/json" },
1285
+ body: JSON.stringify({ code: code.trim().toUpperCase() })
1286
+ }
1287
+ );
1288
+ if (response.status === 429) {
1289
+ return {
1290
+ valid: false,
1291
+ message: "Too many attempts. Please try again later."
1292
+ };
1293
+ }
1294
+ if (!response.ok) {
1295
+ throw new Error(`Validation request failed: ${response.status}`);
1296
+ }
1297
+ return await response.json();
1298
+ } catch (error) {
1299
+ console.error("Error validating invite code:", error);
1300
+ return {
1301
+ valid: false,
1302
+ message: "Unable to validate code. Please try again."
1303
+ };
1304
+ }
1305
+ }
1306
+ function storeBetaCode(code, config = {}) {
1307
+ const key = config.storageKey ?? DEFAULT_CONFIG.storageKey;
1308
+ if (typeof window !== "undefined") {
1309
+ sessionStorage.setItem(key, code.trim().toUpperCase());
1310
+ }
1311
+ }
1312
+ function getStoredBetaCode(config = {}) {
1313
+ const key = config.storageKey ?? DEFAULT_CONFIG.storageKey;
1314
+ if (typeof window !== "undefined") {
1315
+ return sessionStorage.getItem(key);
1316
+ }
1317
+ return null;
1318
+ }
1319
+ function clearStoredBetaCode(config = {}) {
1320
+ const key = config.storageKey ?? DEFAULT_CONFIG.storageKey;
1321
+ if (typeof window !== "undefined") {
1322
+ sessionStorage.removeItem(key);
1323
+ }
1324
+ }
1325
+
1108
1326
  // src/env.ts
1109
1327
  function getRequiredEnv(key) {
1110
1328
  const value = process.env[key];
@@ -1227,20 +1445,27 @@ function getEnvSummary(keys) {
1227
1445
  checkEnvVars,
1228
1446
  checkRateLimit,
1229
1447
  classifyError,
1448
+ clearStoredBetaCode,
1230
1449
  constantTimeEqual,
1231
1450
  containsHtml,
1232
1451
  containsUrls,
1233
1452
  createAuditActor,
1234
1453
  createAuditLogger,
1454
+ createBetaClient,
1235
1455
  createFeatureFlags,
1236
1456
  createMemoryRateLimitStore,
1457
+ createRedisRateLimitStore,
1237
1458
  createSafeTextSchema,
1238
1459
  detectStage,
1460
+ enforceRateLimit,
1461
+ errorResponse,
1239
1462
  escapeHtml,
1240
1463
  extractAuditIp,
1241
1464
  extractAuditRequestId,
1242
1465
  extractAuditUserAgent,
1466
+ extractBearerToken,
1243
1467
  extractClientIp,
1468
+ fetchBetaSettings,
1244
1469
  getBoolEnv,
1245
1470
  getCorrelationId,
1246
1471
  getEndSessionEndpoint,
@@ -1249,6 +1474,7 @@ function getEnvSummary(keys) {
1249
1474
  getOptionalEnv,
1250
1475
  getRateLimitStatus,
1251
1476
  getRequiredEnv,
1477
+ getStoredBetaCode,
1252
1478
  getTokenEndpoint,
1253
1479
  hasAllRoles,
1254
1480
  hasAnyRole,
@@ -1256,13 +1482,17 @@ function getEnvSummary(keys) {
1256
1482
  isAllowlisted,
1257
1483
  isApiError,
1258
1484
  isTokenExpired,
1485
+ isValidBearerToken,
1259
1486
  parseKeycloakRoles,
1260
1487
  refreshKeycloakToken,
1261
1488
  resetRateLimitForKey,
1262
1489
  resolveIdentifier,
1263
1490
  resolveRateLimitIdentifier,
1264
1491
  sanitizeApiError,
1492
+ storeBetaCode,
1265
1493
  stripHtml,
1266
- validateEnvVars
1494
+ validateBetaCode,
1495
+ validateEnvVars,
1496
+ zodErrorResponse
1267
1497
  });
1268
1498
  //# sourceMappingURL=auth.js.map