@mars-stack/core 0.4.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.
Files changed (192) hide show
  1. package/README.md +32 -0
  2. package/cursor/manifest.json +304 -0
  3. package/cursor/rules/mars-composition-patterns.mdc +186 -0
  4. package/cursor/rules/mars-data-access.mdc +26 -0
  5. package/cursor/rules/mars-project-structure.mdc +34 -0
  6. package/cursor/rules/mars-security.mdc +25 -0
  7. package/cursor/rules/mars-testing.mdc +24 -0
  8. package/cursor/rules/mars-ui-conventions.mdc +29 -0
  9. package/cursor/skills/mars-add-api-route/SKILL.md +120 -0
  10. package/cursor/skills/mars-add-audit-log/SKILL.md +373 -0
  11. package/cursor/skills/mars-add-blog/SKILL.md +447 -0
  12. package/cursor/skills/mars-add-command-palette/SKILL.md +438 -0
  13. package/cursor/skills/mars-add-component/SKILL.md +158 -0
  14. package/cursor/skills/mars-add-crud-routes/SKILL.md +221 -0
  15. package/cursor/skills/mars-add-e2e-test/SKILL.md +227 -0
  16. package/cursor/skills/mars-add-error-boundary/SKILL.md +472 -0
  17. package/cursor/skills/mars-add-feature/SKILL.md +174 -0
  18. package/cursor/skills/mars-add-middleware/SKILL.md +135 -0
  19. package/cursor/skills/mars-add-page/SKILL.md +153 -0
  20. package/cursor/skills/mars-add-prisma-model/SKILL.md +148 -0
  21. package/cursor/skills/mars-add-protected-resource/SKILL.md +192 -0
  22. package/cursor/skills/mars-add-role/SKILL.md +156 -0
  23. package/cursor/skills/mars-add-server-action/SKILL.md +167 -0
  24. package/cursor/skills/mars-add-webhook/SKILL.md +192 -0
  25. package/cursor/skills/mars-build-complete-feature/SKILL.md +228 -0
  26. package/cursor/skills/mars-build-dashboard/SKILL.md +211 -0
  27. package/cursor/skills/mars-build-data-table/SKILL.md +284 -0
  28. package/cursor/skills/mars-build-form/SKILL.md +229 -0
  29. package/cursor/skills/mars-build-landing-page/SKILL.md +248 -0
  30. package/cursor/skills/mars-capture-conversation-context/SKILL.md +119 -0
  31. package/cursor/skills/mars-configure-ai/SKILL.md +617 -0
  32. package/cursor/skills/mars-configure-analytics/SKILL.md +413 -0
  33. package/cursor/skills/mars-configure-dark-mode/SKILL.md +309 -0
  34. package/cursor/skills/mars-configure-email/SKILL.md +170 -0
  35. package/cursor/skills/mars-configure-email-verification/SKILL.md +333 -0
  36. package/cursor/skills/mars-configure-feature-flags/SKILL.md +361 -0
  37. package/cursor/skills/mars-configure-i18n/SKILL.md +518 -0
  38. package/cursor/skills/mars-configure-jobs/SKILL.md +500 -0
  39. package/cursor/skills/mars-configure-magic-links/SKILL.md +385 -0
  40. package/cursor/skills/mars-configure-multi-tenancy/SKILL.md +611 -0
  41. package/cursor/skills/mars-configure-notifications/SKILL.md +569 -0
  42. package/cursor/skills/mars-configure-oauth/SKILL.md +217 -0
  43. package/cursor/skills/mars-configure-onboarding/SKILL.md +483 -0
  44. package/cursor/skills/mars-configure-payments/SKILL.md +243 -0
  45. package/cursor/skills/mars-configure-realtime/SKILL.md +733 -0
  46. package/cursor/skills/mars-configure-search/SKILL.md +581 -0
  47. package/cursor/skills/mars-configure-storage/SKILL.md +273 -0
  48. package/cursor/skills/mars-configure-two-factor/SKILL.md +518 -0
  49. package/cursor/skills/mars-create-execution-plan/SKILL.md +204 -0
  50. package/cursor/skills/mars-create-seed/SKILL.md +191 -0
  51. package/cursor/skills/mars-deploy-to-vercel/SKILL.md +300 -0
  52. package/cursor/skills/mars-design-tokens/SKILL.md +138 -0
  53. package/cursor/skills/mars-setup-billing/SKILL.md +322 -0
  54. package/cursor/skills/mars-setup-project/SKILL.md +104 -0
  55. package/cursor/skills/mars-setup-teams/SKILL.md +688 -0
  56. package/cursor/skills/mars-test-api-route/SKILL.md +219 -0
  57. package/cursor/skills/mars-update-architecture-docs/SKILL.md +189 -0
  58. package/dist/api-error/index.d.ts +27 -0
  59. package/dist/api-error/index.d.ts.map +1 -0
  60. package/dist/api-error/index.js +2 -0
  61. package/dist/auth/credential-tag.d.ts +5 -0
  62. package/dist/auth/credential-tag.d.ts.map +1 -0
  63. package/dist/auth/credential-tag.js +2 -0
  64. package/dist/auth/crypto-utils.d.ts +43 -0
  65. package/dist/auth/crypto-utils.d.ts.map +1 -0
  66. package/dist/auth/crypto-utils.js +1 -0
  67. package/dist/auth/csrf.d.ts +32 -0
  68. package/dist/auth/csrf.d.ts.map +1 -0
  69. package/dist/auth/csrf.js +2 -0
  70. package/dist/auth/hooks/index.d.ts +4 -0
  71. package/dist/auth/hooks/index.d.ts.map +1 -0
  72. package/dist/auth/hooks/index.js +68 -0
  73. package/dist/auth/hooks/useCSRF.d.ts +7 -0
  74. package/dist/auth/hooks/useCSRF.d.ts.map +1 -0
  75. package/dist/auth/hooks/usePasswordStrength.d.ts +17 -0
  76. package/dist/auth/hooks/usePasswordStrength.d.ts.map +1 -0
  77. package/dist/auth/internal-api-key.d.ts +5 -0
  78. package/dist/auth/internal-api-key.d.ts.map +1 -0
  79. package/dist/auth/internal-api-key.js +30 -0
  80. package/dist/auth/link-utils.d.ts +13 -0
  81. package/dist/auth/link-utils.d.ts.map +1 -0
  82. package/dist/auth/link-utils.js +1 -0
  83. package/dist/auth/middleware.d.ts +56 -0
  84. package/dist/auth/middleware.d.ts.map +1 -0
  85. package/dist/auth/middleware.js +3 -0
  86. package/dist/auth/password.d.ts +28 -0
  87. package/dist/auth/password.d.ts.map +1 -0
  88. package/dist/auth/password.js +1 -0
  89. package/dist/auth/reset-token.d.ts +3 -0
  90. package/dist/auth/reset-token.d.ts.map +1 -0
  91. package/dist/auth/reset-token.js +9 -0
  92. package/dist/auth/responses.d.ts +15 -0
  93. package/dist/auth/responses.d.ts.map +1 -0
  94. package/dist/auth/responses.js +2 -0
  95. package/dist/auth/session.d.ts +79 -0
  96. package/dist/auth/session.d.ts.map +1 -0
  97. package/dist/auth/session.js +1 -0
  98. package/dist/auth/types.d.ts +18 -0
  99. package/dist/auth/types.d.ts.map +1 -0
  100. package/dist/auth/types.js +10 -0
  101. package/dist/auth/validation.d.ts +146 -0
  102. package/dist/auth/validation.d.ts.map +1 -0
  103. package/dist/auth/validation.js +116 -0
  104. package/dist/auth/validators.d.ts +4 -0
  105. package/dist/auth/validators.d.ts.map +1 -0
  106. package/dist/auth/validators.js +27 -0
  107. package/dist/auth/verification.d.ts +54 -0
  108. package/dist/auth/verification.d.ts.map +1 -0
  109. package/dist/auth/verification.js +39 -0
  110. package/dist/chunk-4LS3QDD5.js +162 -0
  111. package/dist/chunk-ABBUHT5Z.js +110 -0
  112. package/dist/chunk-CTYAVMOF.js +15 -0
  113. package/dist/chunk-GVLH2GQP.js +14 -0
  114. package/dist/chunk-HOSMMQMA.js +109 -0
  115. package/dist/chunk-MXQ66RUN.js +28 -0
  116. package/dist/chunk-PZE3JGXO.js +149 -0
  117. package/dist/chunk-QAH2Y5WK.js +93 -0
  118. package/dist/chunk-QWMN5UJC.js +76 -0
  119. package/dist/chunk-ROQV54MU.js +117 -0
  120. package/dist/chunk-U4NZQ366.js +46 -0
  121. package/dist/chunk-WBJOIENS.js +22 -0
  122. package/dist/chunk-WO6FHJHG.js +29 -0
  123. package/dist/chunk-Z5BEKPJI.js +96 -0
  124. package/dist/chunk-ZA46T6GX.js +24 -0
  125. package/dist/configure-mars.d.ts +104 -0
  126. package/dist/configure-mars.d.ts.map +1 -0
  127. package/dist/database/index.d.ts +8 -0
  128. package/dist/database/index.d.ts.map +1 -0
  129. package/dist/database/index.js +1 -0
  130. package/dist/email/index.d.ts +25 -0
  131. package/dist/email/index.d.ts.map +1 -0
  132. package/dist/email/index.js +2 -0
  133. package/dist/email/types.d.ts +18 -0
  134. package/dist/email/types.d.ts.map +1 -0
  135. package/dist/env/index.d.ts +36 -0
  136. package/dist/env/index.d.ts.map +1 -0
  137. package/dist/env/index.js +1 -0
  138. package/dist/index.d.ts +6 -0
  139. package/dist/index.d.ts.map +1 -0
  140. package/dist/index.js +163 -0
  141. package/dist/logger/index.d.ts +80 -0
  142. package/dist/logger/index.d.ts.map +1 -0
  143. package/dist/logger/index.js +1 -0
  144. package/dist/payments/index.d.ts +53 -0
  145. package/dist/payments/index.d.ts.map +1 -0
  146. package/dist/payments/index.js +72 -0
  147. package/dist/plugin/builtin/email-plugins.d.ts +10 -0
  148. package/dist/plugin/builtin/email-plugins.d.ts.map +1 -0
  149. package/dist/plugin/builtin/index.d.ts +4 -0
  150. package/dist/plugin/builtin/index.d.ts.map +1 -0
  151. package/dist/plugin/builtin/index.js +324 -0
  152. package/dist/plugin/builtin/payment-plugins.d.ts +4 -0
  153. package/dist/plugin/builtin/payment-plugins.d.ts.map +1 -0
  154. package/dist/plugin/builtin/storage-plugins.d.ts +5 -0
  155. package/dist/plugin/builtin/storage-plugins.d.ts.map +1 -0
  156. package/dist/plugin/index.d.ts +21 -0
  157. package/dist/plugin/index.d.ts.map +1 -0
  158. package/dist/plugin/index.js +30 -0
  159. package/dist/rate-limit/index.d.ts +89 -0
  160. package/dist/rate-limit/index.d.ts.map +1 -0
  161. package/dist/rate-limit/index.js +166 -0
  162. package/dist/seo/faq.d.ts +37 -0
  163. package/dist/seo/faq.d.ts.map +1 -0
  164. package/dist/seo/index.d.ts +75 -0
  165. package/dist/seo/index.d.ts.map +1 -0
  166. package/dist/seo/index.js +1 -0
  167. package/dist/storage/index.d.ts +50 -0
  168. package/dist/storage/index.d.ts.map +1 -0
  169. package/dist/storage/index.js +211 -0
  170. package/dist/test-utils/factories.d.ts +38 -0
  171. package/dist/test-utils/factories.d.ts.map +1 -0
  172. package/dist/test-utils/index.d.ts +6 -0
  173. package/dist/test-utils/index.d.ts.map +1 -0
  174. package/dist/test-utils/index.js +117 -0
  175. package/dist/test-utils/mock-auth.d.ts +25 -0
  176. package/dist/test-utils/mock-auth.d.ts.map +1 -0
  177. package/dist/test-utils/mock-prisma.d.ts +55 -0
  178. package/dist/test-utils/mock-prisma.d.ts.map +1 -0
  179. package/dist/test-utils/render.d.ts +4 -0
  180. package/dist/test-utils/render.d.ts.map +1 -0
  181. package/dist/test-utils/request-helpers.d.ts +6 -0
  182. package/dist/test-utils/request-helpers.d.ts.map +1 -0
  183. package/dist/types.d.ts +53 -0
  184. package/dist/types.d.ts.map +1 -0
  185. package/dist/utils/math.d.ts +2 -0
  186. package/dist/utils/math.d.ts.map +1 -0
  187. package/dist/utils/math.js +7 -0
  188. package/dist/utils/optional-import.d.ts +14 -0
  189. package/dist/utils/optional-import.d.ts.map +1 -0
  190. package/package.json +205 -0
  191. package/scripts/generate-skill-adapters.ts +146 -0
  192. package/scripts/postinstall.mjs +146 -0
@@ -0,0 +1,93 @@
1
+ // src/database/index.ts
2
+ var DatabaseError = class extends Error {
3
+ constructor(message, hint) {
4
+ super(message);
5
+ this.name = "DatabaseError";
6
+ this.hint = hint;
7
+ }
8
+ };
9
+ var ERROR_PATTERNS = [
10
+ {
11
+ test: (msg) => msg.includes("Can't reach database") || msg.includes("Connection refused") || msg.includes("ECONNREFUSED"),
12
+ message: "Cannot connect to the database",
13
+ hint: "Is your database running? Try:\n \u2192 yarn dev (starts embedded local Postgres)\n \u2192 Check your DATABASE_URL in .env\n \u2192 If using Neon/Supabase, check your connection string and network"
14
+ },
15
+ {
16
+ test: (msg) => msg.includes("denied access") || msg.includes("password authentication failed") || msg.includes("authentication failed"),
17
+ message: "Database authentication failed",
18
+ hint: "Check your DATABASE_URL credentials.\n \u2192 Ensure the username and password match your database config"
19
+ },
20
+ {
21
+ test: (msg, code) => msg.includes("does not exist") || msg.includes("not available") || code === "P1003",
22
+ message: "Database does not exist",
23
+ hint: "The database has not been created yet. Run:\n \u2192 yarn db:push (push schema and create tables)\n \u2192 Or: yarn db:migrate dev (run migrations)"
24
+ },
25
+ {
26
+ test: (msg, code) => msg.includes("table") && msg.includes("does not exist") || code === "P2021",
27
+ message: "Database tables are missing",
28
+ hint: "The database exists but tables haven't been created. Run:\n \u2192 yarn db:push (push schema to database)\n \u2192 Or: yarn db:migrate dev (run migrations)"
29
+ },
30
+ {
31
+ test: (_msg, code) => code === "P2002",
32
+ message: "Unique constraint violation",
33
+ hint: "A record with that value already exists."
34
+ },
35
+ {
36
+ test: (msg) => msg.includes("timed out") || msg.includes("ETIMEDOUT"),
37
+ message: "Database connection timed out",
38
+ hint: "The database is not responding. Check:\n \u2192 Is your database host reachable?\n \u2192 Are you behind a firewall or VPN?\n \u2192 If using a cloud provider, check the dashboard for status"
39
+ },
40
+ {
41
+ test: (msg) => msg.includes("SSL") || msg.includes("sslmode"),
42
+ message: "Database SSL connection error",
43
+ hint: "Your database requires SSL. Ensure your DATABASE_URL includes:\n \u2192 ?sslmode=require (for Neon, Supabase, etc.)"
44
+ }
45
+ ];
46
+ function formatDatabaseError(error) {
47
+ if (error instanceof DatabaseError) return error;
48
+ const message = error instanceof Error ? error.message : String(error);
49
+ const code = error?.code;
50
+ for (const pattern of ERROR_PATTERNS) {
51
+ if (pattern.test(message, code)) {
52
+ return new DatabaseError(pattern.message, pattern.hint);
53
+ }
54
+ }
55
+ return new DatabaseError(
56
+ "An unexpected database error occurred",
57
+ "Check the full error log above for details. Common fixes:\n \u2192 Ensure database is running\n \u2192 yarn db:push (ensure schema is up to date)\n \u2192 Check DATABASE_URL in .env"
58
+ );
59
+ }
60
+ function isDatabaseError(error) {
61
+ if (error instanceof DatabaseError) return true;
62
+ const name = error?.name || "";
63
+ return name.startsWith("PrismaClient");
64
+ }
65
+ function createPrismaProxy(factory) {
66
+ let client = null;
67
+ function getClient() {
68
+ if (client) return client;
69
+ const instance = factory();
70
+ if (process.env.NODE_ENV !== "production") {
71
+ const g = globalThis;
72
+ if (!g.__marsjs_prisma) {
73
+ g.__marsjs_prisma = instance;
74
+ }
75
+ client = g.__marsjs_prisma;
76
+ } else {
77
+ client = instance;
78
+ }
79
+ return client;
80
+ }
81
+ return new Proxy({}, {
82
+ get(_target, prop) {
83
+ const c = getClient();
84
+ const value = c[prop];
85
+ if (typeof value === "function") {
86
+ return value.bind(c);
87
+ }
88
+ return value;
89
+ }
90
+ });
91
+ }
92
+
93
+ export { DatabaseError, createPrismaProxy, formatDatabaseError, isDatabaseError };
@@ -0,0 +1,76 @@
1
+ import { AUTH_RESPONSES, validateOwnership } from './chunk-ZA46T6GX.js';
2
+ import { NextResponse } from 'next/server';
3
+
4
+ function createAuthMiddleware(config) {
5
+ function withAuth(handler) {
6
+ return async (request, context) => {
7
+ try {
8
+ const session = await config.verifySession();
9
+ if (!session) return AUTH_RESPONSES.UNAUTHORIZED();
10
+ const authenticatedRequest = request;
11
+ authenticatedRequest.session = session;
12
+ return await handler(authenticatedRequest, context);
13
+ } catch (error) {
14
+ console.error("Authentication error:", error);
15
+ return AUTH_RESPONSES.SERVER_ERROR();
16
+ }
17
+ };
18
+ }
19
+ function withAuthNoParams(handler) {
20
+ return async (request) => {
21
+ try {
22
+ const session = await config.verifySession();
23
+ if (!session) return AUTH_RESPONSES.UNAUTHORIZED();
24
+ const authenticatedRequest = request;
25
+ authenticatedRequest.session = session;
26
+ return await handler(authenticatedRequest);
27
+ } catch (error) {
28
+ console.error("Authentication error:", error);
29
+ return AUTH_RESPONSES.SERVER_ERROR();
30
+ }
31
+ };
32
+ }
33
+ function withAuthSimple(handler) {
34
+ return async (_request) => {
35
+ try {
36
+ const session = await config.verifySession();
37
+ if (!session) return AUTH_RESPONSES.UNAUTHORIZED();
38
+ return await handler();
39
+ } catch (error) {
40
+ console.error("Authentication error:", error);
41
+ return AUTH_RESPONSES.SERVER_ERROR();
42
+ }
43
+ };
44
+ }
45
+ function withRole(allowedRoles, handler) {
46
+ return withAuth(async (request, context) => {
47
+ const dbRole = await config.findUserRole(request.session.userId);
48
+ if (!dbRole || !allowedRoles.includes(dbRole)) {
49
+ return AUTH_RESPONSES.FORBIDDEN();
50
+ }
51
+ request.session.role = dbRole;
52
+ return await handler(request, context);
53
+ });
54
+ }
55
+ function withOwnership(getResourceUserId, handler) {
56
+ return withAuth(async (request, context) => {
57
+ const resourceUserId = await getResourceUserId(request, context);
58
+ if (!resourceUserId) {
59
+ return NextResponse.json({ error: "Resource not found" }, { status: 404 });
60
+ }
61
+ if (!validateOwnership(resourceUserId, request.session.userId)) {
62
+ return AUTH_RESPONSES.FORBIDDEN();
63
+ }
64
+ return await handler(request, context);
65
+ });
66
+ }
67
+ return {
68
+ withAuth,
69
+ withAuthNoParams,
70
+ withAuthSimple,
71
+ withRole,
72
+ withOwnership
73
+ };
74
+ }
75
+
76
+ export { createAuthMiddleware };
@@ -0,0 +1,117 @@
1
+ import pino from 'pino';
2
+
3
+ // src/logger/index.ts
4
+ var SecurityEventType = /* @__PURE__ */ ((SecurityEventType2) => {
5
+ SecurityEventType2["LOGIN_SUCCESS"] = "login_success";
6
+ SecurityEventType2["LOGIN_FAILURE"] = "login_failure";
7
+ SecurityEventType2["LOGOUT"] = "logout";
8
+ SecurityEventType2["SESSION_EXPIRED"] = "session_expired";
9
+ SecurityEventType2["ACCOUNT_CREATED"] = "account_created";
10
+ SecurityEventType2["ACCOUNT_LOCKED"] = "account_locked";
11
+ SecurityEventType2["ACCOUNT_UNLOCKED"] = "account_unlocked";
12
+ SecurityEventType2["EMAIL_VERIFIED"] = "email_verified";
13
+ SecurityEventType2["PASSWORD_CHANGED"] = "password_changed";
14
+ SecurityEventType2["PASSWORD_RESET_REQUESTED"] = "password_reset_requested";
15
+ SecurityEventType2["PASSWORD_RESET_COMPLETED"] = "password_reset_completed";
16
+ SecurityEventType2["CSRF_VIOLATION"] = "csrf_violation";
17
+ SecurityEventType2["RATE_LIMIT_EXCEEDED"] = "rate_limit_exceeded";
18
+ SecurityEventType2["SUSPICIOUS_LOGIN"] = "suspicious_login";
19
+ SecurityEventType2["MULTIPLE_FAILED_LOGINS"] = "multiple_failed_logins";
20
+ SecurityEventType2["SESSION_HIJACK_ATTEMPT"] = "session_hijack_attempt";
21
+ SecurityEventType2["ADMIN_LOGIN"] = "admin_login";
22
+ SecurityEventType2["ADMIN_ACTION"] = "admin_action";
23
+ SecurityEventType2["PRIVILEGE_ESCALATION_ATTEMPT"] = "privilege_escalation_attempt";
24
+ SecurityEventType2["SECURITY_HEADER_VIOLATION"] = "security_header_violation";
25
+ SecurityEventType2["INVALID_TOKEN"] = "invalid_token";
26
+ SecurityEventType2["EXPIRED_TOKEN"] = "expired_token";
27
+ return SecurityEventType2;
28
+ })(SecurityEventType || {});
29
+ function createLogger(config) {
30
+ const logger = pino({
31
+ level: process.env.LOG_LEVEL || "info",
32
+ formatters: {
33
+ level: (label) => ({ level: label })
34
+ },
35
+ timestamp: pino.stdTimeFunctions.isoTime
36
+ });
37
+ const serviceName = config.serviceName.toLowerCase().replace(/\s+/g, "-");
38
+ const appLogger = logger.child({ service: serviceName });
39
+ const authLogger = logger.child({ service: serviceName, domain: "auth" });
40
+ const securityLogger = logger.child({ service: serviceName, domain: "security" });
41
+ const apiLogger = logger.child({ service: serviceName, domain: "api" });
42
+ const logSecurityEvent = {
43
+ loginSuccess: (event) => {
44
+ securityLogger.info({ event_type: "login_success" /* LOGIN_SUCCESS */, ...event }, "User login successful");
45
+ },
46
+ loginFailure: (event) => {
47
+ securityLogger.warn({ event_type: "login_failure" /* LOGIN_FAILURE */, ...event }, `Login failed: ${event.reason}`);
48
+ },
49
+ logout: (event) => {
50
+ securityLogger.info({ event_type: "logout" /* LOGOUT */, ...event }, "User logout");
51
+ },
52
+ accountCreated: (event) => {
53
+ securityLogger.info({ event_type: "account_created" /* ACCOUNT_CREATED */, ...event }, "New account created");
54
+ },
55
+ accountLocked: (event) => {
56
+ securityLogger.error(
57
+ { event_type: "account_locked" /* ACCOUNT_LOCKED */, ...event },
58
+ `Account locked after ${event.failedAttempts} failed attempts`
59
+ );
60
+ },
61
+ emailVerified: (event) => {
62
+ securityLogger.info({ event_type: "email_verified" /* EMAIL_VERIFIED */, ...event }, "Email verified");
63
+ },
64
+ passwordChanged: (event) => {
65
+ securityLogger.info(
66
+ { event_type: "password_changed" /* PASSWORD_CHANGED */, ...event },
67
+ `Password changed via ${event.method}`
68
+ );
69
+ },
70
+ csrfViolation: (event) => {
71
+ securityLogger.error(
72
+ { event_type: "csrf_violation" /* CSRF_VIOLATION */, ...event },
73
+ `CSRF violation detected on ${event.endpoint || "unknown endpoint"}`
74
+ );
75
+ },
76
+ rateLimitExceeded: (event) => {
77
+ securityLogger.warn(
78
+ { event_type: "rate_limit_exceeded" /* RATE_LIMIT_EXCEEDED */, ...event },
79
+ `Rate limit exceeded for ${event.limitType}: ${event.identifier}`
80
+ );
81
+ },
82
+ suspiciousLogin: (event) => {
83
+ securityLogger.error(
84
+ { event_type: "suspicious_login" /* SUSPICIOUS_LOGIN */, ...event },
85
+ `Suspicious login detected: ${event.reason}`
86
+ );
87
+ },
88
+ adminLogin: (event) => {
89
+ securityLogger.info(
90
+ { event_type: "admin_login" /* ADMIN_LOGIN */, ...event, requires_audit: true },
91
+ "Admin user login"
92
+ );
93
+ },
94
+ adminAction: (event) => {
95
+ securityLogger.info(
96
+ { event_type: "admin_action" /* ADMIN_ACTION */, ...event, requires_audit: true },
97
+ `Admin action: ${event.action}`
98
+ );
99
+ },
100
+ invalidToken: (event) => {
101
+ securityLogger.warn(
102
+ { event_type: "invalid_token" /* INVALID_TOKEN */, ...event },
103
+ `Invalid ${event.tokenType} token`
104
+ );
105
+ }
106
+ };
107
+ return {
108
+ logger,
109
+ appLogger,
110
+ authLogger,
111
+ securityLogger,
112
+ apiLogger,
113
+ logSecurityEvent
114
+ };
115
+ }
116
+
117
+ export { SecurityEventType, createLogger };
@@ -0,0 +1,46 @@
1
+ import { isDatabaseError, formatDatabaseError } from './chunk-QAH2Y5WK.js';
2
+ import { NextResponse } from 'next/server';
3
+ import { z } from 'zod';
4
+
5
+ function logDatabaseError(logger, error, endpoint) {
6
+ const separator = "\u2500".repeat(60);
7
+ const lines = [
8
+ "",
9
+ separator,
10
+ ` DATABASE ERROR on ${endpoint}`,
11
+ separator,
12
+ "",
13
+ ` ${error.message}`,
14
+ "",
15
+ " How to fix:",
16
+ ...error.hint.split("\n").map((line) => ` ${line}`),
17
+ "",
18
+ separator,
19
+ ""
20
+ ];
21
+ logger.error(lines.join("\n"));
22
+ }
23
+ function createApiErrorHandler(config) {
24
+ function handleApiError(error, options) {
25
+ const { endpoint, fallbackMessage = "An unexpected error occurred" } = options;
26
+ if (error instanceof z.ZodError) {
27
+ return NextResponse.json({ error: error.issues[0].message }, { status: 400 });
28
+ }
29
+ if (isDatabaseError(error)) {
30
+ const dbError = formatDatabaseError(error);
31
+ logDatabaseError(config.logger, dbError, endpoint);
32
+ return NextResponse.json(
33
+ { error: "Service temporarily unavailable. Please try again later." },
34
+ { status: 503 }
35
+ );
36
+ }
37
+ config.logger.error(
38
+ { error: error instanceof Error ? error.message : String(error), endpoint },
39
+ `Unhandled error on ${endpoint}`
40
+ );
41
+ return NextResponse.json({ error: fallbackMessage }, { status: 500 });
42
+ }
43
+ return { handleApiError };
44
+ }
45
+
46
+ export { createApiErrorHandler };
@@ -0,0 +1,22 @@
1
+ // src/auth/link-utils.ts
2
+ function normalizeEmailFromQueryParam(rawEmail) {
3
+ return rawEmail.trim().replace(/ /g, "+");
4
+ }
5
+ function buildEmailVerificationUrl({
6
+ baseUrl,
7
+ token
8
+ }) {
9
+ return new URL(`/verify/${token}`, baseUrl).toString();
10
+ }
11
+ function buildPasswordResetUrl({
12
+ baseUrl,
13
+ token,
14
+ email
15
+ }) {
16
+ const url = new URL("/reset-password", baseUrl);
17
+ url.searchParams.set("token", token);
18
+ url.searchParams.set("email", email);
19
+ return url.toString();
20
+ }
21
+
22
+ export { buildEmailVerificationUrl, buildPasswordResetUrl, normalizeEmailFromQueryParam };
@@ -0,0 +1,29 @@
1
+ import 'server-only';
2
+ import { z } from 'zod';
3
+
4
+ // src/auth/password.ts
5
+ var PASSWORD_PREHASH_DOMAIN = "mars-password-v1";
6
+ async function sha256Prehash(password) {
7
+ const payload = `${PASSWORD_PREHASH_DOMAIN}:${password}`;
8
+ const data = new TextEncoder().encode(payload);
9
+ const digest = await crypto.subtle.digest("SHA-256", data);
10
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
11
+ }
12
+ var passwordSchema = z.string().min(8, "Password must be at least 8 characters").max(100, "Password must be less than 100 characters").regex(/[A-Z]/, "Password must contain at least one uppercase letter").regex(/[a-z]/, "Password must contain at least one lowercase letter").regex(/[0-9]/, "Password must contain at least one number").regex(/[^A-Za-z0-9]/, "Password must contain at least one special character").refine(
13
+ (password) => !password.toLowerCase().includes("password"),
14
+ "Password cannot contain 'password'"
15
+ ).refine((password) => !password.includes("123"), "Password cannot contain sequential numbers");
16
+ async function hashPassword(password) {
17
+ const { hash } = await import('bcryptjs');
18
+ const prehashed = await sha256Prehash(password);
19
+ return hash(prehashed, 12);
20
+ }
21
+ async function verifyPassword(plainPassword, hashedPassword) {
22
+ const { compare } = await import('bcryptjs');
23
+ const prehashed = await sha256Prehash(plainPassword);
24
+ const matches = await compare(prehashed, hashedPassword);
25
+ if (matches) return true;
26
+ return compare(plainPassword, hashedPassword);
27
+ }
28
+
29
+ export { hashPassword, passwordSchema, verifyPassword };
@@ -0,0 +1,96 @@
1
+ import 'server-only';
2
+ import { z } from 'zod';
3
+
4
+ // src/env/index.ts
5
+ var emptyToUndefined = z.literal("").transform(() => void 0);
6
+ function optionalString() {
7
+ return emptyToUndefined.or(z.string());
8
+ }
9
+ function optionalUrl() {
10
+ return emptyToUndefined.or(z.string().url());
11
+ }
12
+ function optionalEmail() {
13
+ return emptyToUndefined.or(z.string().email());
14
+ }
15
+ function getEnvironment() {
16
+ if (process.env.VERCEL_ENV === "production") return "production";
17
+ if (process.env.VERCEL_ENV === "preview") return "staging";
18
+ return "development";
19
+ }
20
+ function createEnvValidator(config) {
21
+ const isBuildTime = process.env.NODE_ENV === "production" && (!process.env.VERCEL_ENV || process.env.NEXT_PHASE === "phase-production-build");
22
+ function buildEnvSchema() {
23
+ const isDev = process.env.NODE_ENV === "development";
24
+ const isProd = process.env.NODE_ENV === "production";
25
+ const jwtSecretSchema = isBuildTime ? optionalString().optional() : z.string().min(32).refine(
26
+ (s) => new Set(s.split("")).size >= 16,
27
+ "JWT_SECRET should have at least 16 unique characters for sufficient entropy"
28
+ );
29
+ const base = {
30
+ DATABASE_URL: isBuildTime || isDev ? optionalUrl().optional() : z.string().url(),
31
+ APP_URL: optionalUrl().optional(),
32
+ JWT_SECRET: jwtSecretSchema,
33
+ LOG_LEVEL: optionalString().optional(),
34
+ COMING_SOON: optionalString().optional()
35
+ };
36
+ if (config.features.auth) {
37
+ base.GOOGLE_CLIENT_ID = optionalString().optional();
38
+ base.GOOGLE_CLIENT_SECRET = optionalString().optional();
39
+ }
40
+ const emailProvider = config.services.email?.provider;
41
+ if (emailProvider && emailProvider !== "console") {
42
+ base.SENDGRID_API_KEY = isProd && !isBuildTime ? z.string().min(1, "SENDGRID_API_KEY required when email provider is sendgrid") : optionalString().optional();
43
+ base.SENDGRID_FROM_EMAIL = optionalEmail().optional();
44
+ }
45
+ const storageProvider = config.services.storage?.provider;
46
+ if (storageProvider === "vercel") {
47
+ base.BLOB_READ_WRITE_TOKEN = isProd && !isBuildTime ? z.string().min(1, "BLOB_READ_WRITE_TOKEN required when storage provider is vercel") : optionalString().optional();
48
+ } else if (storageProvider === "s3") {
49
+ base.AWS_ACCESS_KEY_ID = isProd && !isBuildTime ? z.string().min(1, "AWS_ACCESS_KEY_ID required when storage provider is s3") : optionalString().optional();
50
+ base.AWS_SECRET_ACCESS_KEY = isProd && !isBuildTime ? z.string().min(1, "AWS_SECRET_ACCESS_KEY required when storage provider is s3") : optionalString().optional();
51
+ base.AWS_REGION = optionalString().optional();
52
+ base.S3_BUCKET_NAME = isProd && !isBuildTime ? z.string().min(1, "S3_BUCKET_NAME required when storage provider is s3") : optionalString().optional();
53
+ }
54
+ const paymentsProvider = config.services.payments?.provider;
55
+ if (paymentsProvider && paymentsProvider !== "none") {
56
+ base.STRIPE_SECRET_KEY = isProd && !isBuildTime ? z.string().min(1, "STRIPE_SECRET_KEY required when payments provider is configured") : optionalString().optional();
57
+ base.STRIPE_WEBHOOK_SECRET = isProd && !isBuildTime ? z.string().min(1, "STRIPE_WEBHOOK_SECRET required when payments provider is configured") : optionalString().optional();
58
+ base.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY = optionalString().optional();
59
+ }
60
+ base.UPSTASH_REDIS_REST_URL = optionalUrl().optional();
61
+ base.UPSTASH_REDIS_REST_TOKEN = optionalString().optional();
62
+ base.KV_REST_API_URL = optionalUrl().optional();
63
+ base.KV_REST_API_TOKEN = optionalString().optional();
64
+ return z.object(base);
65
+ }
66
+ let _env = null;
67
+ function parseEnv() {
68
+ const result = buildEnvSchema().safeParse(process.env);
69
+ if (result.success) return result.data;
70
+ const issues = result.error.issues.map((i) => ` \u2192 ${i.path.join(".")}: ${i.message}`).join("\n");
71
+ throw new Error(
72
+ `Environment validation failed:
73
+ ${issues}
74
+
75
+ Check your .env file. Run \`mars doctor\` to diagnose.`
76
+ );
77
+ }
78
+ const env = new Proxy({}, {
79
+ get(_target, prop) {
80
+ if (!_env) {
81
+ _env = parseEnv();
82
+ }
83
+ return _env[prop];
84
+ }
85
+ });
86
+ function validateJWTSecret() {
87
+ const secret = env.JWT_SECRET;
88
+ if (!secret) {
89
+ throw new Error("JWT_SECRET is required at runtime");
90
+ }
91
+ return secret;
92
+ }
93
+ return { env, validateJWTSecret, getEnvironment };
94
+ }
95
+
96
+ export { createEnvValidator, getEnvironment };
@@ -0,0 +1,24 @@
1
+ import { constantTimeEqual } from './chunk-MXQ66RUN.js';
2
+ import 'server-only';
3
+ import { NextResponse } from 'next/server';
4
+
5
+ var NO_STORE_HEADERS = {
6
+ "Cache-Control": "no-store, no-cache",
7
+ Pragma: "no-cache"
8
+ };
9
+ var AUTH_RESPONSES = {
10
+ UNAUTHORIZED: () => NextResponse.json(
11
+ { error: "Authentication required" },
12
+ { status: 401, headers: NO_STORE_HEADERS }
13
+ ),
14
+ FORBIDDEN: () => NextResponse.json({ error: "Access denied" }, { status: 403, headers: NO_STORE_HEADERS }),
15
+ SERVER_ERROR: () => NextResponse.json(
16
+ { error: "Internal server error" },
17
+ { status: 500, headers: NO_STORE_HEADERS }
18
+ )
19
+ };
20
+ function validateOwnership(resourceUserId, sessionUserId) {
21
+ return constantTimeEqual(resourceUserId, sessionUserId);
22
+ }
23
+
24
+ export { AUTH_RESPONSES, validateOwnership };
@@ -0,0 +1,104 @@
1
+ import type { MarsAppConfig, PrismaLike } from './types';
2
+ interface ConfigureMarsOptions {
3
+ appConfig: MarsAppConfig;
4
+ prisma: PrismaLike;
5
+ signInRoute: string;
6
+ }
7
+ export declare function configureMars(options: ConfigureMarsOptions): {
8
+ logger: import("pino").Logger<never, boolean>;
9
+ appLogger: import("pino").Logger<never, boolean>;
10
+ authLogger: import("pino").Logger<never, boolean>;
11
+ securityLogger: import("pino").Logger<never, boolean>;
12
+ apiLogger: import("pino").Logger<never, boolean>;
13
+ logSecurityEvent: {
14
+ loginSuccess: (event: Omit<import(".").SecurityEvent, "type">) => void;
15
+ loginFailure: (event: Omit<import(".").SecurityEvent, "type"> & {
16
+ reason: string;
17
+ }) => void;
18
+ logout: (event: Omit<import(".").SecurityEvent, "type">) => void;
19
+ accountCreated: (event: Omit<import(".").SecurityEvent, "type">) => void;
20
+ accountLocked: (event: Omit<import(".").SecurityEvent, "type"> & {
21
+ failedAttempts: number;
22
+ }) => void;
23
+ emailVerified: (event: Omit<import(".").SecurityEvent, "type">) => void;
24
+ passwordChanged: (event: Omit<import(".").SecurityEvent, "type"> & {
25
+ method: "reset" | "change";
26
+ }) => void;
27
+ csrfViolation: (event: Omit<import(".").SecurityEvent, "type"> & {
28
+ endpoint?: string;
29
+ }) => void;
30
+ rateLimitExceeded: (event: Omit<import(".").SecurityEvent, "type"> & {
31
+ identifier: string;
32
+ limitType: string;
33
+ }) => void;
34
+ suspiciousLogin: (event: Omit<import(".").SecurityEvent, "type"> & {
35
+ reason: string;
36
+ }) => void;
37
+ adminLogin: (event: Omit<import(".").SecurityEvent, "type">) => void;
38
+ adminAction: (event: Omit<import(".").SecurityEvent, "type"> & {
39
+ action: string;
40
+ targetUserId?: string;
41
+ }) => void;
42
+ invalidToken: (event: Omit<import(".").SecurityEvent, "type"> & {
43
+ tokenType: string;
44
+ }) => void;
45
+ };
46
+ sendEmail: (params: import(".").SendEmailParams) => Promise<void>;
47
+ env: Record<string, unknown>;
48
+ validateJWTSecret: () => string;
49
+ getEnvironment: typeof import("./env/index").getEnvironment;
50
+ createSession: (user: {
51
+ id: string;
52
+ email: string;
53
+ name: string;
54
+ role: string;
55
+ emailVerified: boolean;
56
+ credentialTag: string;
57
+ }) => Promise<void>;
58
+ getSession: () => Promise<import(".").SessionPayload | null>;
59
+ updateSession: () => Promise<void>;
60
+ deleteSession: () => Promise<void>;
61
+ verifySession: () => Promise<import(".").SessionPayload>;
62
+ verifySessionForAPI: () => Promise<import(".").SessionPayload | null>;
63
+ getCurrentUser: () => Promise<{
64
+ id: string;
65
+ email: string;
66
+ name: string;
67
+ role: string;
68
+ emailVerified: boolean;
69
+ } | null>;
70
+ generateCSRFToken: (sessionFingerprint?: string) => Promise<string>;
71
+ verifyCSRFToken: (token: string, expectedFingerprint?: string) => Promise<boolean>;
72
+ requireCSRFToken: (sessionFingerprint?: string) => Promise<string>;
73
+ validateCSRFRequest: (request: import("next/server").NextRequest, sessionFingerprint?: string) => Promise<boolean>;
74
+ withAuth: (handler: import("./auth/middleware").AuthenticatedAPIHandler) => (request: import("next/server").NextRequest, context: {
75
+ params: Promise<{
76
+ [key: string]: string;
77
+ }>;
78
+ }) => Promise<import("next/server").NextResponse>;
79
+ withAuthNoParams: (handler: import("./auth/middleware").AuthenticatedAPIHandlerNoParams) => (request: import("next/server").NextRequest) => Promise<import("next/server").NextResponse>;
80
+ withAuthSimple: (handler: import("./auth/middleware").AuthenticatedAPIHandlerSimple) => (_request: import("next/server").NextRequest) => Promise<import("next/server").NextResponse>;
81
+ withRole: (allowedRoles: string[], handler: import("./auth/middleware").AuthenticatedAPIHandler) => (request: import("next/server").NextRequest, context: {
82
+ params: Promise<{
83
+ [key: string]: string;
84
+ }>;
85
+ }) => Promise<import("next/server").NextResponse>;
86
+ withOwnership: (getResourceUserId: (request: import("./auth/middleware").AuthenticatedRequest, context: {
87
+ params: Promise<{
88
+ [key: string]: string;
89
+ }>;
90
+ }) => Promise<string | null | undefined>, handler: import("./auth/middleware").AuthenticatedAPIHandler) => (request: import("next/server").NextRequest, context: {
91
+ params: Promise<{
92
+ [key: string]: string;
93
+ }>;
94
+ }) => Promise<import("next/server").NextResponse>;
95
+ handleApiError: (error: unknown, options: import("./api-error/index").ApiErrorOptions) => import("next/server").NextResponse;
96
+ buildOrganizationJsonLd: () => Record<string, unknown>;
97
+ buildWebSiteJsonLd: () => Record<string, unknown>;
98
+ buildSiteNavigationJsonLd: (items: import("./seo/index").SiteNavItem[]) => Record<string, unknown>;
99
+ buildBreadcrumbListJsonLd: (items: import("./seo/index").BreadcrumbItem[]) => Record<string, unknown>;
100
+ buildArticleJsonLd: (article: import("./seo/index").ArticleJsonLdInput) => Record<string, unknown>;
101
+ getBaseUrl: (request?: Request) => string;
102
+ };
103
+ export {};
104
+ //# sourceMappingURL=configure-mars.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configure-mars.d.ts","sourceRoot":"","sources":["../src/configure-mars.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAYzD,UAAU,oBAAoB;IAC5B,SAAS,EAAE,aAAa,CAAC;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAoDD,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB;;;;;;;;;;;;;;;;;;;;;oBAqCvC,CAAC;;;;;;;;;;;;wBAmCX,CAAR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iFA3FE,0CAA2B,EAAC;;;;KAGpB;iGAmBV,0CAAoB;6FAkBX,2CAAqB;;;;;;;;;;;;;;;;;;;;;2BAoCA,OAAO,KAAG,MAAM;EAmF/C"}
@@ -0,0 +1,8 @@
1
+ export declare class DatabaseError extends Error {
2
+ readonly hint: string;
3
+ constructor(message: string, hint: string);
4
+ }
5
+ export declare function formatDatabaseError(error: unknown): DatabaseError;
6
+ export declare function isDatabaseError(error: unknown): boolean;
7
+ export declare function createPrismaProxy<T extends object>(factory: () => T): T;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/database/index.ts"],"names":[],"mappings":"AAAA,qBAAa,aAAc,SAAQ,KAAK;IACtC,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAEjB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAK1C;AAqED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CAmBjE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIvD;AAED,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,CA+BvE"}
@@ -0,0 +1 @@
1
+ export { DatabaseError, createPrismaProxy, formatDatabaseError, isDatabaseError } from '../chunk-QAH2Y5WK.js';
@@ -0,0 +1,25 @@
1
+ import type { SendEmailParams, EmailServiceError } from './types';
2
+ export type { SendEmailParams, EmailServiceError };
3
+ export interface EmailProvider {
4
+ send(params: SendEmailParams): Promise<void>;
5
+ }
6
+ interface EmailServiceConfig {
7
+ provider: string;
8
+ appName: string;
9
+ }
10
+ export declare function formatConsoleEmail(params: SendEmailParams): string;
11
+ /**
12
+ * Creates an email service backed by the configured provider (SendGrid, Resend, or console).
13
+ * Provider selection is determined by the `config.provider` string; unknown providers
14
+ * fall back to the console logger.
15
+ *
16
+ * @param config - Configuration with provider name and app display name
17
+ * @returns An object with a `sendEmail` method
18
+ * @example
19
+ * const email = createEmailService({ provider: 'resend', appName: 'Acme' });
20
+ * await email.sendEmail({ to: 'user@example.com', subject: 'Hello', html: '<p>Hi</p>' });
21
+ */
22
+ export declare function createEmailService(config: EmailServiceConfig): {
23
+ sendEmail: (params: SendEmailParams) => Promise<void>;
24
+ };
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/email/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAGlE,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,CAAC;AAEnD,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C;AAED,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AA2ED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CA0BlE;AAgBD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB;wBAI1B,eAAe,KAAG,OAAO,CAAC,IAAI,CAAC;EAKjE"}
@@ -0,0 +1,2 @@
1
+ export { createEmailService, formatConsoleEmail } from '../chunk-HOSMMQMA.js';
2
+ import '../chunk-CTYAVMOF.js';