@lastshotlabs/bunshot 0.0.20 → 0.0.25

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.
Files changed (122) hide show
  1. package/README.md +3035 -1249
  2. package/dist/adapters/localStorage.d.ts +6 -0
  3. package/dist/adapters/localStorage.js +44 -0
  4. package/dist/adapters/memoryAuth.d.ts +7 -0
  5. package/dist/adapters/memoryAuth.js +144 -0
  6. package/dist/adapters/memoryStorage.d.ts +3 -0
  7. package/dist/adapters/memoryStorage.js +44 -0
  8. package/dist/adapters/mongoAuth.js +120 -0
  9. package/dist/adapters/s3Storage.d.ts +14 -0
  10. package/dist/adapters/s3Storage.js +126 -0
  11. package/dist/adapters/sqliteAuth.d.ts +7 -0
  12. package/dist/adapters/sqliteAuth.js +199 -0
  13. package/dist/app.d.ts +100 -3
  14. package/dist/app.js +248 -47
  15. package/dist/cli.js +118 -38
  16. package/dist/index.d.ts +49 -7
  17. package/dist/index.js +35 -5
  18. package/dist/lib/HttpError.d.ts +5 -0
  19. package/dist/lib/HttpError.js +7 -0
  20. package/dist/lib/appConfig.d.ts +44 -0
  21. package/dist/lib/appConfig.js +16 -0
  22. package/dist/lib/auditLog.d.ts +52 -0
  23. package/dist/lib/auditLog.js +201 -0
  24. package/dist/lib/authAdapter.d.ts +69 -0
  25. package/dist/lib/constants.d.ts +4 -0
  26. package/dist/lib/constants.js +4 -0
  27. package/dist/lib/context.d.ts +19 -1
  28. package/dist/lib/context.js +17 -3
  29. package/dist/lib/createRoute.d.ts +28 -2
  30. package/dist/lib/createRoute.js +54 -3
  31. package/dist/lib/deletionCancelToken.d.ts +12 -0
  32. package/dist/lib/deletionCancelToken.js +88 -0
  33. package/dist/lib/groups.d.ts +113 -0
  34. package/dist/lib/groups.js +133 -0
  35. package/dist/lib/idempotency.d.ts +22 -0
  36. package/dist/lib/idempotency.js +182 -0
  37. package/dist/lib/metrics.d.ts +14 -0
  38. package/dist/lib/metrics.js +158 -0
  39. package/dist/lib/pagination.d.ts +119 -0
  40. package/dist/lib/pagination.js +166 -0
  41. package/dist/lib/session.d.ts +4 -0
  42. package/dist/lib/session.js +56 -2
  43. package/dist/lib/signing.d.ts +52 -0
  44. package/dist/lib/signing.js +180 -0
  45. package/dist/lib/storageAdapter.d.ts +30 -0
  46. package/dist/lib/storageAdapter.js +1 -0
  47. package/dist/lib/stripUnreferencedSchemas.d.ts +11 -0
  48. package/dist/lib/stripUnreferencedSchemas.js +79 -0
  49. package/dist/lib/tenant.js +2 -2
  50. package/dist/lib/upload.d.ts +35 -0
  51. package/dist/lib/upload.js +87 -0
  52. package/dist/lib/validate.js +2 -2
  53. package/dist/lib/ws.d.ts +1 -0
  54. package/dist/lib/ws.js +21 -0
  55. package/dist/lib/wsHeartbeat.d.ts +12 -0
  56. package/dist/lib/wsHeartbeat.js +57 -0
  57. package/dist/lib/wsMessages.d.ts +40 -0
  58. package/dist/lib/wsMessages.js +330 -0
  59. package/dist/lib/wsPresence.d.ts +25 -0
  60. package/dist/lib/wsPresence.js +99 -0
  61. package/dist/middleware/auditLog.d.ts +22 -0
  62. package/dist/middleware/auditLog.js +39 -0
  63. package/dist/middleware/cacheResponse.js +5 -1
  64. package/dist/middleware/csrf.js +10 -0
  65. package/dist/middleware/identify.js +57 -9
  66. package/dist/middleware/metrics.d.ts +9 -0
  67. package/dist/middleware/metrics.js +26 -0
  68. package/dist/middleware/requestId.d.ts +3 -0
  69. package/dist/middleware/requestId.js +7 -0
  70. package/dist/middleware/requestLogger.d.ts +38 -0
  71. package/dist/middleware/requestLogger.js +68 -0
  72. package/dist/middleware/requestSigning.d.ts +20 -0
  73. package/dist/middleware/requestSigning.js +99 -0
  74. package/dist/middleware/requireMfaSetup.d.ts +16 -0
  75. package/dist/middleware/requireMfaSetup.js +36 -0
  76. package/dist/middleware/requireRole.d.ts +9 -3
  77. package/dist/middleware/requireRole.js +23 -36
  78. package/dist/middleware/upload.d.ts +5 -0
  79. package/dist/middleware/upload.js +27 -0
  80. package/dist/middleware/webhookAuth.d.ts +30 -0
  81. package/dist/middleware/webhookAuth.js +57 -0
  82. package/dist/models/AuditLog.d.ts +30 -0
  83. package/dist/models/AuditLog.js +39 -0
  84. package/dist/models/Group.d.ts +21 -0
  85. package/dist/models/Group.js +28 -0
  86. package/dist/models/GroupMembership.d.ts +21 -0
  87. package/dist/models/GroupMembership.js +25 -0
  88. package/dist/routes/auth.js +84 -6
  89. package/dist/routes/groups.d.ts +21 -0
  90. package/dist/routes/groups.js +346 -0
  91. package/dist/routes/jobs.js +47 -45
  92. package/dist/routes/metrics.d.ts +7 -0
  93. package/dist/routes/metrics.js +52 -0
  94. package/dist/routes/mfa.js +4 -0
  95. package/dist/routes/uploads.d.ts +2 -0
  96. package/dist/routes/uploads.js +135 -0
  97. package/dist/server.d.ts +26 -0
  98. package/dist/server.js +46 -3
  99. package/dist/ws/index.js +3 -0
  100. package/docs/sections/auth-flow/full.md +779 -634
  101. package/docs/sections/auth-flow/overview.md +2 -2
  102. package/docs/sections/auth-security-examples/full.md +365 -0
  103. package/docs/sections/authentication/full.md +130 -0
  104. package/docs/sections/authentication/overview.md +5 -0
  105. package/docs/sections/cli/full.md +13 -1
  106. package/docs/sections/configuration/full.md +17 -0
  107. package/docs/sections/configuration/overview.md +1 -0
  108. package/docs/sections/exports/full.md +34 -3
  109. package/docs/sections/logging/full.md +83 -0
  110. package/docs/sections/metrics/full.md +127 -0
  111. package/docs/sections/oauth/full.md +189 -189
  112. package/docs/sections/oauth/overview.md +1 -1
  113. package/docs/sections/pagination/full.md +93 -0
  114. package/docs/sections/roles/full.md +224 -135
  115. package/docs/sections/roles/overview.md +3 -1
  116. package/docs/sections/signing/full.md +203 -0
  117. package/docs/sections/uploads/full.md +199 -0
  118. package/docs/sections/versioning/full.md +85 -0
  119. package/docs/sections/webhook-auth/full.md +100 -0
  120. package/docs/sections/websocket/full.md +83 -0
  121. package/docs/sections/websocket-rooms/full.md +6 -1
  122. package/package.json +16 -4
package/dist/index.d.ts CHANGED
@@ -1,28 +1,34 @@
1
1
  export { createApp } from "./app";
2
2
  export { createServer } from "./server";
3
- export type { CreateAppConfig, ModelSchemasConfig, DbConfig, AppMeta, AuthConfig, AuthRateLimitConfig, AccountDeletionConfig, OAuthConfig, SecurityConfig, CsrfConfig, BotProtectionConfig, PrimaryField, EmailVerificationConfig, PasswordResetConfig, RefreshTokenConfig, MfaConfig, MfaEmailOtpConfig, MfaWebAuthnConfig, JobsConfig, TenancyConfig, TenantConfig } from "./app";
3
+ export type { CreateAppConfig, ModelSchemasConfig, DbConfig, AppMeta, AuthConfig, AuthRateLimitConfig, AccountDeletionConfig, OAuthConfig, SecurityConfig, CsrfConfig, BotProtectionConfig, PrimaryField, EmailVerificationConfig, PasswordResetConfig, RefreshTokenConfig, MfaConfig, MfaEmailOtpConfig, MfaWebAuthnConfig, JobsConfig, TenancyConfig, TenantConfig, LoggingConfig, MetricsConfig, ValidationConfig, VersioningConfig, SigningConfig } from "./app";
4
4
  export type { PasswordPolicyConfig } from "./lib/appConfig";
5
5
  export type { CreateServerConfig, WsConfig } from "./server";
6
6
  export { appConnection, authConnection, mongoose, connectMongo, connectAuthMongo, connectAppMongo, disconnectMongo } from "./lib/mongo";
7
7
  export { connectRedis, disconnectRedis, getRedis } from "./lib/redis";
8
8
  export { getAppRoles } from "./lib/appConfig";
9
- export { HttpError } from "./lib/HttpError";
10
- export { COOKIE_TOKEN, HEADER_USER_TOKEN, COOKIE_REFRESH_TOKEN, HEADER_REFRESH_TOKEN, COOKIE_CSRF_TOKEN, HEADER_CSRF_TOKEN } from "./lib/constants";
9
+ export { HttpError, ValidationError } from "./lib/HttpError";
10
+ export { COOKIE_TOKEN, HEADER_USER_TOKEN, COOKIE_REFRESH_TOKEN, HEADER_REFRESH_TOKEN, COOKIE_CSRF_TOKEN, HEADER_CSRF_TOKEN, HEADER_REQUEST_ID, HEADER_IDEMPOTENCY_KEY, HEADER_SIGNATURE, HEADER_TIMESTAMP } from "./lib/constants";
11
11
  export { createRouter } from "./lib/context";
12
- export { createRoute, withSecurity, registerSchema, registerSchemas } from "./lib/createRoute";
12
+ export { createRoute, withSecurity, registerSchema, registerSchemas, setVersionPrefix, clearVersionPrefix } from "./lib/createRoute";
13
+ export { stripUnreferencedSchemas } from "./lib/stripUnreferencedSchemas";
13
14
  export { zodToMongoose } from "./lib/zodToMongoose";
14
15
  export type { ZodToMongooseConfig, ZodToMongooseRefConfig } from "./lib/zodToMongoose";
15
16
  export { createDtoMapper } from "./lib/createDtoMapper";
16
17
  export type { DtoMapperConfig } from "./lib/createDtoMapper";
17
- export type { AppEnv, AppVariables } from "./lib/context";
18
+ export type { AppEnv, AppVariables, ValidationErrorFormatter, DefaultValidationErrorBody, ValidationErrorDetail } from "./lib/context";
19
+ export { defaultValidationErrorFormatter } from "./lib/context";
18
20
  export { signToken, verifyToken } from "./lib/jwt";
19
21
  export { log } from "./lib/logger";
20
22
  export { createResetToken, consumeResetToken, setPasswordResetStore } from "./lib/resetPassword";
23
+ export { createDeletionCancelToken, consumeDeletionCancelToken, setDeletionCancelTokenStore } from "./lib/deletionCancelToken";
21
24
  export { timingSafeEqual, sha256 } from "./lib/crypto";
25
+ export { hmacSign, hmacVerify, signCookieValue, verifyCookieValue, signCursor, verifyCursor, createPresignedUrl, verifyPresignedUrl } from "./lib/signing";
26
+ export { idempotent, setIdempotencyStore, clearIdempotencyMemoryStore } from "./lib/idempotency";
27
+ export type { IdempotencyOptions } from "./lib/idempotency";
22
28
  export { getClientIp, setTrustProxy } from "./lib/clientIp";
23
29
  export { storeOAuthCode, consumeOAuthCode, setOAuthCodeStore } from "./lib/oauthCode";
24
30
  export type { OAuthCodePayload } from "./lib/oauthCode";
25
- export { createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount, evictOldestSession, updateSessionLastActive, setSessionStore, deleteUserSessions, setRefreshToken, getSessionByRefreshToken, rotateRefreshToken } from "./lib/session";
31
+ export { createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount, evictOldestSession, updateSessionLastActive, setSessionStore, deleteUserSessions, setRefreshToken, getSessionByRefreshToken, rotateRefreshToken, getSessionFingerprint, setSessionFingerprint } from "./lib/session";
26
32
  export type { SessionMetadata, SessionInfo, RefreshResult } from "./lib/session";
27
33
  export { createVerificationToken, getVerificationToken, deleteVerificationToken } from "./lib/emailVerification";
28
34
  export { createMfaChallenge, consumeMfaChallenge, replaceMfaChallengeOtp, setMfaChallengeStore, createWebAuthnRegistrationChallenge, consumeWebAuthnRegistrationChallenge, clearMemoryMfaChallenges } from "./lib/mfaChallenge";
@@ -39,10 +45,25 @@ export type { RateLimitOptions } from "./middleware/rateLimit";
39
45
  export { userAuth } from "./middleware/userAuth";
40
46
  export { requireRole } from "./middleware/requireRole";
41
47
  export { requireVerifiedEmail } from "./middleware/requireVerifiedEmail";
48
+ export { requireMfaSetup } from "./middleware/requireMfaSetup";
42
49
  export { csrfProtection, refreshCsrfToken, clearCsrfToken } from "./middleware/csrf";
43
50
  export type { CsrfMiddlewareOptions } from "./middleware/csrf";
44
51
  export { cacheResponse, bustCache, bustCachePattern, setCacheStore, getCacheModel } from "./middleware/cacheResponse";
52
+ export { webhookAuth } from "./middleware/webhookAuth";
53
+ export type { WebhookAuthOptions, WebhookTimestampOptions } from "./middleware/webhookAuth";
54
+ export { requireSignedRequest } from "./middleware/requestSigning";
55
+ export type { RequestSigningOptions } from "./middleware/requestSigning";
56
+ export { auditLog } from "./middleware/auditLog";
57
+ export type { AuditLogMiddlewareOptions } from "./middleware/auditLog";
58
+ export { requestId } from "./middleware/requestId";
59
+ export { requestLogger } from "./middleware/requestLogger";
60
+ export type { RequestLogEntry, RequestLoggerOptions, LogLevel } from "./middleware/requestLogger";
61
+ export { metricsCollector } from "./middleware/metrics";
62
+ export type { MetricsMiddlewareOptions } from "./middleware/metrics";
45
63
  export { buildFingerprint } from "./lib/fingerprint";
64
+ export { logAuditEntry, getAuditLogs, clearAuditLogMemoryStore } from "./lib/auditLog";
65
+ export { resetMetrics, incrementCounter, observeHistogram, registerGaugeCallback, serializeMetrics, closeMetricsQueues } from "./lib/metrics";
66
+ export type { AuditLogEntry, AuditLogOptions, AuditLogQuery } from "./lib/auditLog";
46
67
  export { sqliteAuthAdapter, setSqliteDb, startSqliteCleanup } from "./adapters/sqliteAuth";
47
68
  export { memoryAuthAdapter, clearMemoryStore } from "./adapters/memoryAuth";
48
69
  export { setUserRoles, addUserRole, removeUserRole, getTenantRoles, setTenantRoles, addTenantRole, removeTenantRole } from "./lib/roles";
@@ -50,7 +71,28 @@ export type { AuthAdapter, OAuthProfile, WebAuthnCredential } from "./lib/authAd
50
71
  export type { OAuthProviderConfig } from "./lib/oauth";
51
72
  export { websocket, createWsUpgradeHandler } from "./ws/index";
52
73
  export type { SocketData } from "./ws/index";
53
- export { publish, subscribe, unsubscribe, getSubscriptions, handleRoomActions, getRooms, getRoomSubscribers } from "./lib/ws";
74
+ export { publish, subscribe, unsubscribe, getSubscriptions, handleRoomActions, getRooms, getRoomSubscribers, setPresenceEnabled } from "./lib/ws";
75
+ export { registerSocket, deregisterSocket, handlePong, startHeartbeat, stopHeartbeat, clearHeartbeatState } from "./lib/wsHeartbeat";
76
+ export type { HeartbeatConfig } from "./lib/wsHeartbeat";
77
+ export { trackSocket, untrackSocket, addPresence, removePresence, cleanupPresence, getRoomPresence, getUserPresence, clearPresenceStore } from "./lib/wsPresence";
78
+ export { persistMessage, getMessageHistory, configureRoom, setWsMessageStore, setWsMessageDefaults, clearWsMessageMemoryStore } from "./lib/wsMessages";
79
+ export type { StoredMessage, WsMessageStore, WsMessageDefaults, RoomPersistenceConfig } from "./lib/wsMessages";
54
80
  export { createTenant, deleteTenant, getTenant, listTenants } from "./lib/tenant";
55
81
  export type { TenantInfo, CreateTenantOptions } from "./lib/tenant";
56
82
  export { invalidateTenantCache } from "./middleware/tenant";
83
+ export { createGroup, deleteGroup, getGroup, listGroups, updateGroup, addGroupMember, updateGroupMembership, removeGroupMember, getGroupMembers, getUserGroups, getEffectiveRoles, } from "./lib/groups";
84
+ export type { GroupRecord, GroupMembershipRecord, PaginationOpts, PaginatedResult } from "./lib/groups";
85
+ export type { GroupsConfig, GroupsManagementConfig } from "./routes/groups";
86
+ export { offsetParams, parseOffsetParams, paginatedResponse, cursorParams, parseCursorParams, cursorResponse, maybeSignCursor, } from "./lib/pagination";
87
+ export type { OffsetParamDefaults, ParsedOffsetParams, CursorParamDefaults, ParsedCursorParams, CursorResult, } from "./lib/pagination";
88
+ export { handleUpload } from "./middleware/upload";
89
+ export type { UploadMiddlewareOptions } from "./middleware/upload";
90
+ export { parseUpload, setStorageAdapter, getStorageAdapter, setUploadConfig, getUploadConfig } from "./lib/upload";
91
+ export type { UploadOpts } from "./lib/upload";
92
+ export type { StorageAdapter, UploadResult } from "./lib/storageAdapter";
93
+ export type { UploadConfig, PresignedUrlConfig } from "./app";
94
+ export { memoryStorage, clearMemoryUploadStore } from "./adapters/memoryStorage";
95
+ export { localStorage } from "./adapters/localStorage";
96
+ export type { LocalStorageConfig } from "./adapters/localStorage";
97
+ export { s3Storage } from "./adapters/s3Storage";
98
+ export type { S3StorageConfig } from "./adapters/s3Storage";
package/dist/index.js CHANGED
@@ -6,19 +6,24 @@ export { appConnection, authConnection, mongoose, connectMongo, connectAuthMongo
6
6
  export { connectRedis, disconnectRedis, getRedis } from "./lib/redis";
7
7
  // Lib utilities
8
8
  export { getAppRoles } from "./lib/appConfig";
9
- export { HttpError } from "./lib/HttpError";
10
- export { COOKIE_TOKEN, HEADER_USER_TOKEN, COOKIE_REFRESH_TOKEN, HEADER_REFRESH_TOKEN, COOKIE_CSRF_TOKEN, HEADER_CSRF_TOKEN } from "./lib/constants";
9
+ export { HttpError, ValidationError } from "./lib/HttpError";
10
+ export { COOKIE_TOKEN, HEADER_USER_TOKEN, COOKIE_REFRESH_TOKEN, HEADER_REFRESH_TOKEN, COOKIE_CSRF_TOKEN, HEADER_CSRF_TOKEN, HEADER_REQUEST_ID, HEADER_IDEMPOTENCY_KEY, HEADER_SIGNATURE, HEADER_TIMESTAMP } from "./lib/constants";
11
11
  export { createRouter } from "./lib/context";
12
- export { createRoute, withSecurity, registerSchema, registerSchemas } from "./lib/createRoute";
12
+ export { createRoute, withSecurity, registerSchema, registerSchemas, setVersionPrefix, clearVersionPrefix } from "./lib/createRoute";
13
+ export { stripUnreferencedSchemas } from "./lib/stripUnreferencedSchemas";
13
14
  export { zodToMongoose } from "./lib/zodToMongoose";
14
15
  export { createDtoMapper } from "./lib/createDtoMapper";
16
+ export { defaultValidationErrorFormatter } from "./lib/context";
15
17
  export { signToken, verifyToken } from "./lib/jwt";
16
18
  export { log } from "./lib/logger";
17
19
  export { createResetToken, consumeResetToken, setPasswordResetStore } from "./lib/resetPassword";
20
+ export { createDeletionCancelToken, consumeDeletionCancelToken, setDeletionCancelTokenStore } from "./lib/deletionCancelToken";
18
21
  export { timingSafeEqual, sha256 } from "./lib/crypto";
22
+ export { hmacSign, hmacVerify, signCookieValue, verifyCookieValue, signCursor, verifyCursor, createPresignedUrl, verifyPresignedUrl } from "./lib/signing";
23
+ export { idempotent, setIdempotencyStore, clearIdempotencyMemoryStore } from "./lib/idempotency";
19
24
  export { getClientIp, setTrustProxy } from "./lib/clientIp";
20
25
  export { storeOAuthCode, consumeOAuthCode, setOAuthCodeStore } from "./lib/oauthCode";
21
- export { createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount, evictOldestSession, updateSessionLastActive, setSessionStore, deleteUserSessions, setRefreshToken, getSessionByRefreshToken, rotateRefreshToken } from "./lib/session";
26
+ export { createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount, evictOldestSession, updateSessionLastActive, setSessionStore, deleteUserSessions, setRefreshToken, getSessionByRefreshToken, rotateRefreshToken, getSessionFingerprint, setSessionFingerprint } from "./lib/session";
22
27
  export { createVerificationToken, getVerificationToken, deleteVerificationToken } from "./lib/emailVerification";
23
28
  export { createMfaChallenge, consumeMfaChallenge, replaceMfaChallengeOtp, setMfaChallengeStore, createWebAuthnRegistrationChallenge, consumeWebAuthnRegistrationChallenge, clearMemoryMfaChallenges } from "./lib/mfaChallenge";
24
29
  export { bustAuthLimit, trackAttempt, isLimited, clearMemoryRateLimitStore } from "./lib/authRateLimit";
@@ -31,17 +36,42 @@ export { rateLimit } from "./middleware/rateLimit";
31
36
  export { userAuth } from "./middleware/userAuth";
32
37
  export { requireRole } from "./middleware/requireRole";
33
38
  export { requireVerifiedEmail } from "./middleware/requireVerifiedEmail";
39
+ export { requireMfaSetup } from "./middleware/requireMfaSetup";
34
40
  export { csrfProtection, refreshCsrfToken, clearCsrfToken } from "./middleware/csrf";
35
41
  export { cacheResponse, bustCache, bustCachePattern, setCacheStore, getCacheModel } from "./middleware/cacheResponse";
42
+ export { webhookAuth } from "./middleware/webhookAuth";
43
+ export { requireSignedRequest } from "./middleware/requestSigning";
44
+ export { auditLog } from "./middleware/auditLog";
45
+ export { requestId } from "./middleware/requestId";
46
+ export { requestLogger } from "./middleware/requestLogger";
47
+ export { metricsCollector } from "./middleware/metrics";
36
48
  // Lib utilities (bot protection)
37
49
  export { buildFingerprint } from "./lib/fingerprint";
50
+ export { logAuditEntry, getAuditLogs, clearAuditLogMemoryStore } from "./lib/auditLog";
51
+ export { resetMetrics, incrementCounter, observeHistogram, registerGaugeCallback, serializeMetrics, closeMetricsQueues } from "./lib/metrics";
38
52
  // Models
39
53
  export { sqliteAuthAdapter, setSqliteDb, startSqliteCleanup } from "./adapters/sqliteAuth";
40
54
  export { memoryAuthAdapter, clearMemoryStore } from "./adapters/memoryAuth";
41
55
  export { setUserRoles, addUserRole, removeUserRole, getTenantRoles, setTenantRoles, addTenantRole, removeTenantRole } from "./lib/roles";
42
56
  // WebSocket
43
57
  export { websocket, createWsUpgradeHandler } from "./ws/index";
44
- export { publish, subscribe, unsubscribe, getSubscriptions, handleRoomActions, getRooms, getRoomSubscribers } from "./lib/ws";
58
+ export { publish, subscribe, unsubscribe, getSubscriptions, handleRoomActions, getRooms, getRoomSubscribers, setPresenceEnabled } from "./lib/ws";
59
+ // WebSocket — Heartbeat
60
+ export { registerSocket, deregisterSocket, handlePong, startHeartbeat, stopHeartbeat, clearHeartbeatState } from "./lib/wsHeartbeat";
61
+ // WebSocket — Presence
62
+ export { trackSocket, untrackSocket, addPresence, removePresence, cleanupPresence, getRoomPresence, getUserPresence, clearPresenceStore } from "./lib/wsPresence";
63
+ // WebSocket — Message Persistence
64
+ export { persistMessage, getMessageHistory, configureRoom, setWsMessageStore, setWsMessageDefaults, clearWsMessageMemoryStore } from "./lib/wsMessages";
45
65
  // Tenancy
46
66
  export { createTenant, deleteTenant, getTenant, listTenants } from "./lib/tenant";
47
67
  export { invalidateTenantCache } from "./middleware/tenant";
68
+ // Groups
69
+ export { createGroup, deleteGroup, getGroup, listGroups, updateGroup, addGroupMember, updateGroupMembership, removeGroupMember, getGroupMembers, getUserGroups, getEffectiveRoles, } from "./lib/groups";
70
+ // Pagination helpers
71
+ export { offsetParams, parseOffsetParams, paginatedResponse, cursorParams, parseCursorParams, cursorResponse, maybeSignCursor, } from "./lib/pagination";
72
+ // Upload
73
+ export { handleUpload } from "./middleware/upload";
74
+ export { parseUpload, setStorageAdapter, getStorageAdapter, setUploadConfig, getUploadConfig } from "./lib/upload";
75
+ export { memoryStorage, clearMemoryUploadStore } from "./adapters/memoryStorage";
76
+ export { localStorage } from "./adapters/localStorage";
77
+ export { s3Storage } from "./adapters/s3Storage";
@@ -2,3 +2,8 @@ export declare class HttpError extends Error {
2
2
  status: number;
3
3
  constructor(status: number, message: string);
4
4
  }
5
+ import type { ZodIssue } from "zod";
6
+ export declare class ValidationError extends HttpError {
7
+ readonly issues: ZodIssue[];
8
+ constructor(issues: ZodIssue[]);
9
+ }
@@ -5,3 +5,10 @@ export class HttpError extends Error {
5
5
  this.status = status;
6
6
  }
7
7
  }
8
+ export class ValidationError extends HttpError {
9
+ issues;
10
+ constructor(issues) {
11
+ super(400, "Validation failed");
12
+ this.issues = issues;
13
+ }
14
+ }
@@ -102,6 +102,8 @@ export interface MfaConfig {
102
102
  emailOtp?: MfaEmailOtpConfig;
103
103
  /** WebAuthn/FIDO2 configuration. When set, enables security key MFA routes. */
104
104
  webauthn?: MfaWebAuthnConfig;
105
+ /** When true, authenticated users must complete MFA setup before accessing non-auth endpoints. Default: false. */
106
+ required?: boolean;
105
107
  }
106
108
  export declare const setMfaConfig: (config: MfaConfig | null) => void;
107
109
  export declare const getMfaConfig: () => MfaConfig | null;
@@ -114,5 +116,47 @@ export declare const getMfaChallengeTtl: () => number;
114
116
  export declare const getMfaEmailOtpConfig: () => MfaEmailOtpConfig | null;
115
117
  export declare const getMfaEmailOtpCodeLength: () => number;
116
118
  export declare const getMfaWebAuthnConfig: () => MfaWebAuthnConfig | null;
119
+ export declare const getMfaRequired: () => boolean;
117
120
  export declare const setCsrfEnabled: (v: boolean) => void;
118
121
  export declare const getCsrfEnabled: () => boolean;
122
+ export interface SigningConfig {
123
+ /**
124
+ * HMAC secret. Defaults to JWT_SECRET_DEV/JWT_SECRET_PROD env var if omitted.
125
+ * Pass string[] to support key rotation — first element signs, all elements verify.
126
+ */
127
+ secret?: string | string[];
128
+ /** Sign/verify cookie values set via exported helpers. Default: false. */
129
+ cookies?: boolean;
130
+ /** Sign pagination cursor tokens to prevent client tampering. Default: false. */
131
+ cursors?: boolean;
132
+ /** HMAC-based stateless presigned URLs (no DB lookup). Default: false. */
133
+ presignedUrls?: boolean | {
134
+ defaultExpiry?: number;
135
+ };
136
+ /** Require clients to HMAC-sign requests (method+path+timestamp+body). Default: false. */
137
+ requestSigning?: boolean | {
138
+ tolerance?: number;
139
+ header?: string;
140
+ timestampHeader?: string;
141
+ };
142
+ /** Hash idempotency keys before storage. Default: false. */
143
+ idempotencyKeys?: boolean;
144
+ /** Bind sessions to client IP+UA fingerprint. Default: false. */
145
+ sessionBinding?: boolean | {
146
+ fields?: Array<"ip" | "ua" | "accept-language">;
147
+ /**
148
+ * What to do when fingerprint doesn't match.
149
+ * - "unauthenticate": treat as logged-out (default — graceful but masks attacks)
150
+ * - "reject": return 401 (strict — recommended for security-conscious apps)
151
+ * - "log-only": allow through but log the mismatch (useful during rollout)
152
+ */
153
+ onMismatch?: "unauthenticate" | "reject" | "log-only";
154
+ };
155
+ }
156
+ export declare const setSigningConfig: (config: SigningConfig | null) => void;
157
+ export declare const getSigningConfig: () => SigningConfig | null;
158
+ /**
159
+ * Returns the active signing secret: signing.secret → JWT_SECRET_PROD/DEV env var.
160
+ * Returns null when neither is configured — callers must handle this gracefully.
161
+ */
162
+ export declare const getSigningSecret: () => string | string[] | null;
@@ -59,9 +59,25 @@ export const getMfaChallengeTtl = () => _mfaConfig?.challengeTtlSeconds ?? 300;
59
59
  export const getMfaEmailOtpConfig = () => _mfaConfig?.emailOtp ?? null;
60
60
  export const getMfaEmailOtpCodeLength = () => _mfaConfig?.emailOtp?.codeLength ?? 6;
61
61
  export const getMfaWebAuthnConfig = () => _mfaConfig?.webauthn ?? null;
62
+ export const getMfaRequired = () => _mfaConfig?.required ?? false;
62
63
  // ---------------------------------------------------------------------------
63
64
  // CSRF config
64
65
  // ---------------------------------------------------------------------------
65
66
  let _csrfEnabled = false;
66
67
  export const setCsrfEnabled = (v) => { _csrfEnabled = v; };
67
68
  export const getCsrfEnabled = () => _csrfEnabled;
69
+ let _signingConfig = null;
70
+ export const setSigningConfig = (config) => { _signingConfig = config; };
71
+ export const getSigningConfig = () => _signingConfig;
72
+ /**
73
+ * Returns the active signing secret: signing.secret → JWT_SECRET_PROD/DEV env var.
74
+ * Returns null when neither is configured — callers must handle this gracefully.
75
+ */
76
+ export const getSigningSecret = () => {
77
+ if (_signingConfig?.secret)
78
+ return _signingConfig.secret;
79
+ const isProd = process.env.NODE_ENV === "production";
80
+ const envKey = isProd ? "JWT_SECRET_PROD" : "JWT_SECRET_DEV";
81
+ const rawSecret = process.env[envKey];
82
+ return rawSecret ?? null;
83
+ };
@@ -0,0 +1,52 @@
1
+ import type { Database } from "bun:sqlite";
2
+ export interface AuditLogEntry {
3
+ id: string;
4
+ userId: string | null;
5
+ sessionId: string | null;
6
+ tenantId: string | null;
7
+ method: string;
8
+ path: string;
9
+ status: number;
10
+ ip: string | null;
11
+ userAgent: string | null;
12
+ action?: string;
13
+ resource?: string;
14
+ resourceId?: string;
15
+ meta?: Record<string, unknown>;
16
+ requestId?: string;
17
+ /** ISO 8601 string across all backends. */
18
+ createdAt: string;
19
+ /** MongoDB TTL only — silently ignored by SQLite and memory stores. */
20
+ expiresAt?: Date;
21
+ }
22
+ export type AuditLogStore = "mongo" | "sqlite" | "memory";
23
+ export interface AuditLogOptions {
24
+ store: AuditLogStore;
25
+ /** Required when `store === "sqlite"`. */
26
+ db?: Database;
27
+ }
28
+ export interface AuditLogQuery {
29
+ userId?: string;
30
+ tenantId?: string;
31
+ after?: Date | string;
32
+ before?: Date | string;
33
+ /** Default 50, max 200. */
34
+ limit?: number;
35
+ /** Default 0. */
36
+ offset?: number;
37
+ }
38
+ export declare function clearAuditLogMemoryStore(): void;
39
+ /**
40
+ * Persist an audit log entry to the configured store.
41
+ * Errors are caught internally — this function never throws, to ensure
42
+ * storage failures never fail the HTTP request.
43
+ */
44
+ export declare function logAuditEntry(entry: AuditLogEntry, options: AuditLogOptions): Promise<void>;
45
+ /**
46
+ * Query audit log entries from the configured store.
47
+ * Returns `{ items, total }` where `total` is the filtered count before pagination.
48
+ */
49
+ export declare function getAuditLogs(query: AuditLogQuery, options: AuditLogOptions): Promise<{
50
+ items: AuditLogEntry[];
51
+ total: number;
52
+ }>;
@@ -0,0 +1,201 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Memory store
3
+ // ---------------------------------------------------------------------------
4
+ let _auditLogs = [];
5
+ export function clearAuditLogMemoryStore() {
6
+ _auditLogs = [];
7
+ }
8
+ // ---------------------------------------------------------------------------
9
+ // SQLite helpers
10
+ // ---------------------------------------------------------------------------
11
+ function ensureSqliteTable(db) {
12
+ // No module-level flag — CREATE IF NOT EXISTS is idempotent and cheap.
13
+ // A flag would break when multiple Database instances are used (e.g. in tests).
14
+ db.run(`
15
+ CREATE TABLE IF NOT EXISTS audit_logs (
16
+ id TEXT PRIMARY KEY,
17
+ userId TEXT,
18
+ sessionId TEXT,
19
+ tenantId TEXT,
20
+ method TEXT NOT NULL,
21
+ path TEXT NOT NULL,
22
+ status INTEGER NOT NULL,
23
+ ip TEXT,
24
+ userAgent TEXT,
25
+ action TEXT,
26
+ resource TEXT,
27
+ resourceId TEXT,
28
+ meta TEXT,
29
+ createdAt TEXT NOT NULL
30
+ )
31
+ `);
32
+ db.run("CREATE INDEX IF NOT EXISTS idx_al_user ON audit_logs(userId, createdAt)");
33
+ db.run("CREATE INDEX IF NOT EXISTS idx_al_tenant ON audit_logs(tenantId, createdAt)");
34
+ db.run("CREATE INDEX IF NOT EXISTS idx_al_path ON audit_logs(path)");
35
+ }
36
+ // ---------------------------------------------------------------------------
37
+ // logAuditEntry
38
+ // ---------------------------------------------------------------------------
39
+ /**
40
+ * Persist an audit log entry to the configured store.
41
+ * Errors are caught internally — this function never throws, to ensure
42
+ * storage failures never fail the HTTP request.
43
+ */
44
+ export async function logAuditEntry(entry, options) {
45
+ try {
46
+ if (options.store === "memory") {
47
+ _auditLogs.push(entry);
48
+ return;
49
+ }
50
+ if (options.store === "sqlite") {
51
+ const db = options.db;
52
+ if (!db)
53
+ throw new Error("AuditLog: store is 'sqlite' but no db instance was provided");
54
+ ensureSqliteTable(db);
55
+ db.run(`INSERT INTO audit_logs
56
+ (id, userId, sessionId, tenantId, method, path, status,
57
+ ip, userAgent, action, resource, resourceId, meta, createdAt)
58
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
59
+ entry.id,
60
+ entry.userId ?? null,
61
+ entry.sessionId ?? null,
62
+ entry.tenantId ?? null,
63
+ entry.method,
64
+ entry.path,
65
+ entry.status,
66
+ entry.ip ?? null,
67
+ entry.userAgent ?? null,
68
+ entry.action ?? null,
69
+ entry.resource ?? null,
70
+ entry.resourceId ?? null,
71
+ entry.meta !== undefined ? JSON.stringify(entry.meta) : null,
72
+ entry.createdAt,
73
+ ]);
74
+ return;
75
+ }
76
+ if (options.store === "mongo") {
77
+ // Lazy import to avoid bundling mongoose when not used
78
+ const { AuditLog } = await import("../models/AuditLog");
79
+ await AuditLog.create({
80
+ ...entry,
81
+ createdAt: new Date(entry.createdAt),
82
+ });
83
+ return;
84
+ }
85
+ }
86
+ catch (err) {
87
+ console.error("[auditLog] failed to write entry:", err);
88
+ }
89
+ }
90
+ // ---------------------------------------------------------------------------
91
+ // getAuditLogs
92
+ // ---------------------------------------------------------------------------
93
+ /**
94
+ * Query audit log entries from the configured store.
95
+ * Returns `{ items, total }` where `total` is the filtered count before pagination.
96
+ */
97
+ export async function getAuditLogs(query, options) {
98
+ const limit = Math.min(query.limit ?? 50, 200);
99
+ const offset = query.offset ?? 0;
100
+ const after = query.after ? new Date(query.after).toISOString() : undefined;
101
+ const before = query.before ? new Date(query.before).toISOString() : undefined;
102
+ // --- Memory ---
103
+ if (options.store === "memory") {
104
+ let filtered = _auditLogs.slice();
105
+ if (query.userId !== undefined)
106
+ filtered = filtered.filter(e => e.userId === query.userId);
107
+ if (query.tenantId !== undefined)
108
+ filtered = filtered.filter(e => e.tenantId === query.tenantId);
109
+ if (after)
110
+ filtered = filtered.filter(e => e.createdAt >= after);
111
+ if (before)
112
+ filtered = filtered.filter(e => e.createdAt < before);
113
+ return { items: filtered.slice(offset, offset + limit), total: filtered.length };
114
+ }
115
+ // --- SQLite ---
116
+ if (options.store === "sqlite") {
117
+ const db = options.db;
118
+ if (!db)
119
+ throw new Error("AuditLog: store is 'sqlite' but no db instance was provided");
120
+ ensureSqliteTable(db);
121
+ const conditions = [];
122
+ const params = [];
123
+ if (query.userId !== undefined) {
124
+ conditions.push("userId = ?");
125
+ params.push(query.userId);
126
+ }
127
+ if (query.tenantId !== undefined) {
128
+ conditions.push("tenantId = ?");
129
+ params.push(query.tenantId);
130
+ }
131
+ if (after) {
132
+ conditions.push("createdAt >= ?");
133
+ params.push(after);
134
+ }
135
+ if (before) {
136
+ conditions.push("createdAt < ?");
137
+ params.push(before);
138
+ }
139
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
140
+ const { count } = db.query(`SELECT COUNT(*) as count FROM audit_logs ${where}`).get(...params) ?? { count: 0 };
141
+ const rows = db.query(`SELECT * FROM audit_logs ${where} ORDER BY createdAt DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
142
+ const items = rows.map(row => ({
143
+ id: row.id,
144
+ userId: row.userId ?? null,
145
+ sessionId: row.sessionId ?? null,
146
+ tenantId: row.tenantId ?? null,
147
+ method: row.method,
148
+ path: row.path,
149
+ status: row.status,
150
+ ip: row.ip ?? null,
151
+ userAgent: row.userAgent ?? null,
152
+ action: row.action ?? undefined,
153
+ resource: row.resource ?? undefined,
154
+ resourceId: row.resourceId ?? undefined,
155
+ meta: row.meta ? JSON.parse(row.meta) : undefined,
156
+ createdAt: row.createdAt,
157
+ }));
158
+ return { items, total: count };
159
+ }
160
+ // --- MongoDB ---
161
+ if (options.store === "mongo") {
162
+ const { AuditLog } = await import("../models/AuditLog");
163
+ const filter = {};
164
+ if (query.userId !== undefined)
165
+ filter.userId = query.userId;
166
+ if (query.tenantId !== undefined)
167
+ filter.tenantId = query.tenantId;
168
+ if (after || before) {
169
+ filter.createdAt = {
170
+ ...(after ? { $gte: new Date(after) } : {}),
171
+ ...(before ? { $lt: new Date(before) } : {}),
172
+ };
173
+ }
174
+ const [total, docs] = await Promise.all([
175
+ AuditLog.countDocuments(filter),
176
+ AuditLog.find(filter)
177
+ .sort({ createdAt: -1 })
178
+ .skip(offset)
179
+ .limit(limit)
180
+ .lean(),
181
+ ]);
182
+ const items = docs.map(doc => ({
183
+ id: doc.id,
184
+ userId: doc.userId ?? null,
185
+ sessionId: doc.sessionId ?? null,
186
+ tenantId: doc.tenantId ?? null,
187
+ method: doc.method,
188
+ path: doc.path,
189
+ status: doc.status,
190
+ ip: doc.ip ?? null,
191
+ userAgent: doc.userAgent ?? null,
192
+ action: doc.action,
193
+ resource: doc.resource,
194
+ resourceId: doc.resourceId,
195
+ meta: doc.meta,
196
+ createdAt: doc.createdAt.toISOString(),
197
+ }));
198
+ return { items, total };
199
+ }
200
+ return { items: [], total: 0 };
201
+ }
@@ -1,3 +1,5 @@
1
+ import type { GroupRecord, GroupMembershipRecord, PaginationOpts, PaginatedResult } from "./groups";
2
+ export type { GroupRecord, GroupMembershipRecord, PaginationOpts, PaginatedResult };
1
3
  export interface OAuthProfile {
2
4
  email?: string;
3
5
  name?: string;
@@ -102,6 +104,73 @@ export interface AuthAdapter {
102
104
  updateWebAuthnCredentialSignCount?(userId: string, credentialId: string, signCount: number): Promise<void>;
103
105
  /** Optional. Find the user who owns a WebAuthn credential. Returns userId or null. Used for cross-user uniqueness checks. */
104
106
  findUserByWebAuthnCredentialId?(credentialId: string): Promise<string | null>;
107
+ /**
108
+ * Create a new group. Returns the new group's id.
109
+ * The name must be a slug (/^[a-z0-9_-]+$/) and unique within its scope.
110
+ * tenantId: null = app-wide group, string = tenant-scoped group.
111
+ */
112
+ createGroup?(group: Omit<GroupRecord, "id" | "createdAt" | "updatedAt">): Promise<{
113
+ id: string;
114
+ }>;
115
+ /**
116
+ * Delete a group and cascade-delete all its memberships.
117
+ * Cascade behavior is adapter-specific (MongoDB: manual deleteMany, SQLite: ON DELETE CASCADE).
118
+ */
119
+ deleteGroup?(groupId: string): Promise<void>;
120
+ /** Get a group by ID. Returns null if not found. */
121
+ getGroup?(groupId: string): Promise<GroupRecord | null>;
122
+ /**
123
+ * List groups scoped to a tenant (tenantId string) or app-wide (tenantId null).
124
+ * Results are paginated (default limit 50, max 200).
125
+ */
126
+ listGroups?(tenantId: string | null, opts?: PaginationOpts): Promise<PaginatedResult<GroupRecord>>;
127
+ /**
128
+ * Update mutable group fields: name, displayName, description, roles.
129
+ * tenantId is intentionally excluded — it is immutable after creation.
130
+ */
131
+ updateGroup?(groupId: string, updates: Partial<Pick<GroupRecord, "roles" | "name" | "displayName" | "description">>): Promise<void>;
132
+ /**
133
+ * Add a user to a group with optional per-membership roles.
134
+ *
135
+ * CONTRACT: throws if the user is already a member (unique constraint violation).
136
+ * All adapters must surface this as a thrown error, not a silent no-op.
137
+ * Use updateGroupMembership to change roles on an existing membership.
138
+ */
139
+ addGroupMember?(groupId: string, userId: string, roles?: string[]): Promise<void>;
140
+ /**
141
+ * Update the per-membership roles for an existing group member.
142
+ * Replaces the member's roles[] in place (not additive).
143
+ * No updatedAt is tracked — intentional, see GroupMembershipRecord.
144
+ */
145
+ updateGroupMembership?(groupId: string, userId: string, roles: string[]): Promise<void>;
146
+ /** Remove a user from a group. No-op if the user is not a member. */
147
+ removeGroupMember?(groupId: string, userId: string): Promise<void>;
148
+ /** List members of a group with their per-membership roles. Paginated. */
149
+ getGroupMembers?(groupId: string, opts?: PaginationOpts): Promise<PaginatedResult<{
150
+ userId: string;
151
+ roles: string[];
152
+ }>>;
153
+ /**
154
+ * List all groups a user belongs to in the given scope, with their per-membership roles.
155
+ * tenantId = null → app-wide groups; tenantId = string → tenant-scoped groups.
156
+ */
157
+ getUserGroups?(userId: string, tenantId: string | null): Promise<Array<{
158
+ group: GroupRecord;
159
+ membershipRoles: string[];
160
+ }>>;
161
+ /**
162
+ * Return all roles a user effectively has in the given scope, combining:
163
+ * 1. Direct roles (app-wide or tenant-scoped)
164
+ * 2. Group baseline roles (from all groups the user belongs to in that scope)
165
+ * 3. Per-membership roles (user-specific extras within each group)
166
+ *
167
+ * SCOPE CONTRACT (matches requireRole behavior):
168
+ * - tenantId = null → app-wide direct roles + app-wide group roles only
169
+ * - tenantId = string → tenant-scoped direct roles + tenant-scoped group roles only
170
+ *
171
+ * Tenant-scoped group roles NEVER satisfy app-wide role checks and vice versa.
172
+ */
173
+ getEffectiveRoles?(userId: string, tenantId: string | null): Promise<string[]>;
105
174
  }
106
175
  export declare const setAuthAdapter: (adapter: AuthAdapter) => void;
107
176
  export declare const getAuthAdapter: () => AuthAdapter;
@@ -4,3 +4,7 @@ export declare const COOKIE_REFRESH_TOKEN = "refresh_token";
4
4
  export declare const HEADER_REFRESH_TOKEN = "x-refresh-token";
5
5
  export declare const COOKIE_CSRF_TOKEN = "csrf_token";
6
6
  export declare const HEADER_CSRF_TOKEN = "x-csrf-token";
7
+ export declare const HEADER_REQUEST_ID = "x-request-id";
8
+ export declare const HEADER_IDEMPOTENCY_KEY = "idempotency-key";
9
+ export declare const HEADER_SIGNATURE = "x-signature";
10
+ export declare const HEADER_TIMESTAMP = "x-timestamp";
@@ -4,3 +4,7 @@ export const COOKIE_REFRESH_TOKEN = "refresh_token";
4
4
  export const HEADER_REFRESH_TOKEN = "x-refresh-token";
5
5
  export const COOKIE_CSRF_TOKEN = "csrf_token";
6
6
  export const HEADER_CSRF_TOKEN = "x-csrf-token";
7
+ export const HEADER_REQUEST_ID = "x-request-id";
8
+ export const HEADER_IDEMPOTENCY_KEY = "idempotency-key";
9
+ export const HEADER_SIGNATURE = "x-signature";
10
+ export const HEADER_TIMESTAMP = "x-timestamp";