@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,54 @@
1
+ import 'server-only';
2
+ export type VerifyEmailTokenResult = {
3
+ ok: true;
4
+ status: 'verified' | 'already_verified';
5
+ email: string;
6
+ } | {
7
+ ok: false;
8
+ error: 'invalid_or_expired' | 'email_mismatch' | 'user_not_found';
9
+ };
10
+ export interface VerificationTokenRecord {
11
+ identifier: string;
12
+ token: string;
13
+ expires: Date;
14
+ }
15
+ export interface EmailVerificationTokenStore {
16
+ findUnique(args: {
17
+ where: {
18
+ token: string;
19
+ };
20
+ }): Promise<VerificationTokenRecord | null>;
21
+ delete(args: {
22
+ where: {
23
+ token: string;
24
+ };
25
+ }): Promise<unknown>;
26
+ }
27
+ export interface EmailVerificationUserStore {
28
+ findUnique(args: {
29
+ where: {
30
+ email: string;
31
+ };
32
+ select: {
33
+ emailVerified: true;
34
+ };
35
+ }): Promise<{
36
+ emailVerified: Date | null;
37
+ } | null>;
38
+ update(args: {
39
+ where: {
40
+ email: string;
41
+ };
42
+ data: {
43
+ emailVerified: Date;
44
+ };
45
+ }): Promise<unknown>;
46
+ }
47
+ export declare function verifyEmailFromToken(params: {
48
+ token: string;
49
+ email?: string;
50
+ now?: Date;
51
+ verificationTokens: EmailVerificationTokenStore;
52
+ users: EmailVerificationUserStore;
53
+ }): Promise<VerifyEmailTokenResult>;
54
+ //# sourceMappingURL=verification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verification.d.ts","sourceRoot":"","sources":["../../src/auth/verification.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAMrB,MAAM,MAAM,sBAAsB,GAC9B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,UAAU,GAAG,kBAAkB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,oBAAoB,GAAG,gBAAgB,GAAG,gBAAgB,CAAA;CAAE,CAAC;AAErF,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,IAAI,CAAC;CACf;AAED,MAAM,WAAW,2BAA2B;IAC1C,UAAU,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,GAAG,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAC;IACxF,MAAM,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9D;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,CAAC,IAAI,EAAE;QACf,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QACzB,MAAM,EAAE;YAAE,aAAa,EAAE,IAAI,CAAA;SAAE,CAAC;KACjC,GAAG,OAAO,CAAC;QAAE,aAAa,EAAE,IAAI,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IACnD,MAAM,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,IAAI,EAAE;YAAE,aAAa,EAAE,IAAI,CAAA;SAAE,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7F;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,kBAAkB,EAAE,2BAA2B,CAAC;IAChD,KAAK,EAAE,0BAA0B,CAAC;CACnC,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAyClC"}
@@ -0,0 +1,39 @@
1
+ import { hashToken } from '../chunk-MXQ66RUN.js';
2
+ import 'server-only';
3
+
4
+ var VERIFICATION_TOKEN_DOMAIN = "email-verification-v1";
5
+ async function verifyEmailFromToken(params) {
6
+ const now = params.now ?? /* @__PURE__ */ new Date();
7
+ const rawToken = params.token;
8
+ const emailFromRequest = params.email;
9
+ const tokenHash = await hashToken(rawToken, VERIFICATION_TOKEN_DOMAIN);
10
+ const verificationToken = await params.verificationTokens.findUnique({
11
+ where: { token: tokenHash }
12
+ });
13
+ if (!verificationToken || verificationToken.expires < now) {
14
+ return { ok: false, error: "invalid_or_expired" };
15
+ }
16
+ if (emailFromRequest && verificationToken.identifier !== emailFromRequest) {
17
+ return { ok: false, error: "email_mismatch" };
18
+ }
19
+ const emailToVerify = verificationToken.identifier;
20
+ const user = await params.users.findUnique({
21
+ where: { email: emailToVerify },
22
+ select: { emailVerified: true }
23
+ });
24
+ if (!user) return { ok: false, error: "user_not_found" };
25
+ if (!user.emailVerified) {
26
+ await params.users.update({
27
+ where: { email: emailToVerify },
28
+ data: { emailVerified: now }
29
+ });
30
+ }
31
+ await params.verificationTokens.delete({ where: { token: tokenHash } });
32
+ return {
33
+ ok: true,
34
+ status: user.emailVerified ? "already_verified" : "verified",
35
+ email: emailToVerify
36
+ };
37
+ }
38
+
39
+ export { verifyEmailFromToken };
@@ -0,0 +1,162 @@
1
+ import 'server-only';
2
+ import { EncryptJWT, jwtDecrypt } from 'jose';
3
+ import { cookies } from 'next/headers';
4
+ import { redirect } from 'next/navigation';
5
+ import { cache } from 'react';
6
+
7
+ // src/auth/session.ts
8
+ var SESSION_KEY_DOMAIN = "mars-session-v1";
9
+ var _key = null;
10
+ async function deriveKey(secret) {
11
+ if (_key) return _key;
12
+ const encoder = new TextEncoder();
13
+ const keyMaterial = await crypto.subtle.importKey(
14
+ "raw",
15
+ encoder.encode(secret),
16
+ "HKDF",
17
+ false,
18
+ ["deriveBits"]
19
+ );
20
+ const bits = await crypto.subtle.deriveBits(
21
+ {
22
+ name: "HKDF",
23
+ hash: "SHA-256",
24
+ salt: encoder.encode(SESSION_KEY_DOMAIN),
25
+ info: new Uint8Array(0)
26
+ },
27
+ keyMaterial,
28
+ 256
29
+ );
30
+ _key = new Uint8Array(bits);
31
+ return _key;
32
+ }
33
+ var SESSION_COOKIE_NAME = "session";
34
+ var SESSION_IDLE_DURATION = 24 * 60 * 60 * 1e3;
35
+ var SESSION_ABSOLUTE_DURATION = 7 * 24 * 60 * 60 * 1e3;
36
+ var INACTIVITY_TIMEOUT = 30 * 60 * 1e3;
37
+ async function encrypt(payload, secret) {
38
+ return new EncryptJWT(payload).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(new Date(payload.expiresAt)).encrypt(await deriveKey(secret));
39
+ }
40
+ async function decrypt(session = "", secret) {
41
+ if (!session) return null;
42
+ try {
43
+ const { payload } = await jwtDecrypt(session, await deriveKey(secret));
44
+ const sessionPayload = payload;
45
+ if (typeof sessionPayload.createdAt !== "number" || typeof sessionPayload.credentialTag !== "string") {
46
+ return null;
47
+ }
48
+ if (Date.now() > sessionPayload.expiresAt) return null;
49
+ if (Date.now() - sessionPayload.lastActivity > INACTIVITY_TIMEOUT) return null;
50
+ if (Date.now() - sessionPayload.createdAt > SESSION_ABSOLUTE_DURATION) return null;
51
+ return sessionPayload;
52
+ } catch (error) {
53
+ console.warn(
54
+ "Failed to decrypt session (likely legacy cookie):",
55
+ error instanceof Error ? error.message : String(error)
56
+ );
57
+ try {
58
+ const cookieStore = await cookies();
59
+ cookieStore.delete(SESSION_COOKIE_NAME);
60
+ } catch {
61
+ }
62
+ return null;
63
+ }
64
+ }
65
+ function createSessionManager(config) {
66
+ async function createSession(user) {
67
+ const now = Date.now();
68
+ const expiresAt = Math.min(now + SESSION_IDLE_DURATION, now + SESSION_ABSOLUTE_DURATION);
69
+ const fingerprint = crypto.randomUUID();
70
+ const sessionPayload = {
71
+ userId: user.id,
72
+ email: user.email,
73
+ name: user.name,
74
+ role: user.role,
75
+ fingerprint,
76
+ emailVerified: user.emailVerified,
77
+ credentialTag: user.credentialTag,
78
+ createdAt: now,
79
+ expiresAt,
80
+ lastActivity: now
81
+ };
82
+ const encryptedSession = await encrypt(sessionPayload, config.getJWTSecret());
83
+ const cookieStore = await cookies();
84
+ cookieStore.set(SESSION_COOKIE_NAME, encryptedSession, {
85
+ httpOnly: true,
86
+ secure: process.env.NODE_ENV === "production",
87
+ sameSite: "lax",
88
+ path: "/",
89
+ expires: new Date(expiresAt)
90
+ });
91
+ }
92
+ async function getSession() {
93
+ const cookieStore = await cookies();
94
+ const encryptedSession = cookieStore.get(SESSION_COOKIE_NAME)?.value;
95
+ const session = await decrypt(encryptedSession, config.getJWTSecret());
96
+ if (!session) return null;
97
+ const isValidCredential = await config.validateCredential(session.userId, session.credentialTag);
98
+ if (!isValidCredential) {
99
+ cookieStore.delete(SESSION_COOKIE_NAME);
100
+ return null;
101
+ }
102
+ return session;
103
+ }
104
+ async function updateSession() {
105
+ const session = await getSession();
106
+ if (!session) return;
107
+ const now = Date.now();
108
+ const absoluteExpiry = session.createdAt + SESSION_ABSOLUTE_DURATION;
109
+ const nextIdleExpiry = now + SESSION_IDLE_DURATION;
110
+ const updatedSession = {
111
+ ...session,
112
+ lastActivity: now,
113
+ expiresAt: Math.min(nextIdleExpiry, absoluteExpiry)
114
+ };
115
+ const encryptedSession = await encrypt(updatedSession, config.getJWTSecret());
116
+ const cookieStore = await cookies();
117
+ cookieStore.set(SESSION_COOKIE_NAME, encryptedSession, {
118
+ httpOnly: true,
119
+ secure: process.env.NODE_ENV === "production",
120
+ sameSite: "lax",
121
+ path: "/",
122
+ expires: new Date(updatedSession.expiresAt)
123
+ });
124
+ }
125
+ async function deleteSession() {
126
+ const cookieStore = await cookies();
127
+ cookieStore.delete(SESSION_COOKIE_NAME);
128
+ }
129
+ const verifySession = cache(async () => {
130
+ const session = await getSession();
131
+ if (!session) {
132
+ redirect(config.signInRoute);
133
+ }
134
+ return session;
135
+ });
136
+ const verifySessionForAPI = cache(async () => {
137
+ const session = await getSession();
138
+ return session;
139
+ });
140
+ async function getCurrentUser() {
141
+ const session = await getSession();
142
+ if (!session) return null;
143
+ return {
144
+ id: session.userId,
145
+ email: session.email,
146
+ name: session.name,
147
+ role: session.role,
148
+ emailVerified: session.emailVerified
149
+ };
150
+ }
151
+ return {
152
+ createSession,
153
+ getSession,
154
+ updateSession,
155
+ deleteSession,
156
+ verifySession,
157
+ verifySessionForAPI,
158
+ getCurrentUser
159
+ };
160
+ }
161
+
162
+ export { createSessionManager, decrypt, encrypt };
@@ -0,0 +1,110 @@
1
+ // src/seo/faq.ts
2
+ function toPlainTextFromFaqBlocks(blocks) {
3
+ const lines = [];
4
+ for (const block of blocks) {
5
+ if (block.type === "paragraph") {
6
+ const paragraph = block.parts.map((part) => part.type === "text" ? part.value : part.text).join("").replace(/\s+/g, " ").trim();
7
+ if (paragraph) lines.push(paragraph);
8
+ continue;
9
+ }
10
+ const listLine = block.items.map((item) => item.trim()).filter(Boolean).join("; ");
11
+ if (listLine) lines.push(listLine);
12
+ }
13
+ return lines.join("\n");
14
+ }
15
+
16
+ // src/seo/index.ts
17
+ function buildFaqPageJsonLd(items) {
18
+ return {
19
+ "@context": "https://schema.org",
20
+ "@type": "FAQPage",
21
+ mainEntity: items.map((item) => ({
22
+ "@type": "Question",
23
+ name: item.question,
24
+ acceptedAnswer: {
25
+ "@type": "Answer",
26
+ text: item.answer
27
+ }
28
+ }))
29
+ };
30
+ }
31
+ function createSeoBuilders(config) {
32
+ const SITE_URL = config.url;
33
+ const SITE_NAME = config.name;
34
+ function buildOrganizationJsonLd() {
35
+ return {
36
+ "@context": "https://schema.org",
37
+ "@type": "Organization",
38
+ name: SITE_NAME,
39
+ url: SITE_URL,
40
+ ...config.supportEmail && { email: config.supportEmail }
41
+ };
42
+ }
43
+ function buildWebSiteJsonLd() {
44
+ return {
45
+ "@context": "https://schema.org",
46
+ "@type": "WebSite",
47
+ name: SITE_NAME,
48
+ url: SITE_URL
49
+ };
50
+ }
51
+ function buildSiteNavigationJsonLd(items) {
52
+ return {
53
+ "@context": "https://schema.org",
54
+ "@type": "SiteNavigationElement",
55
+ name: "Main Navigation",
56
+ hasPart: items.map((item) => ({
57
+ "@type": "WebPage",
58
+ name: item.name,
59
+ url: new URL(item.url, SITE_URL).toString()
60
+ }))
61
+ };
62
+ }
63
+ function buildBreadcrumbListJsonLd(items) {
64
+ return {
65
+ "@context": "https://schema.org",
66
+ "@type": "BreadcrumbList",
67
+ itemListElement: items.map((item, index) => ({
68
+ "@type": "ListItem",
69
+ position: index + 1,
70
+ name: item.name,
71
+ item: new URL(item.url, SITE_URL).toString()
72
+ }))
73
+ };
74
+ }
75
+ function buildArticleJsonLd(article) {
76
+ return {
77
+ "@context": "https://schema.org",
78
+ "@type": "Article",
79
+ headline: article.title,
80
+ description: article.description,
81
+ url: new URL(`/blog/${article.slug}`, SITE_URL).toString(),
82
+ datePublished: article.date,
83
+ dateModified: article.date,
84
+ author: {
85
+ "@type": "Organization",
86
+ name: article.author,
87
+ url: SITE_URL
88
+ },
89
+ publisher: {
90
+ "@type": "Organization",
91
+ name: SITE_NAME,
92
+ url: SITE_URL
93
+ },
94
+ keywords: article.tags,
95
+ mainEntityOfPage: {
96
+ "@type": "WebPage",
97
+ "@id": new URL(`/blog/${article.slug}`, SITE_URL).toString()
98
+ }
99
+ };
100
+ }
101
+ return {
102
+ buildOrganizationJsonLd,
103
+ buildWebSiteJsonLd,
104
+ buildSiteNavigationJsonLd,
105
+ buildBreadcrumbListJsonLd,
106
+ buildArticleJsonLd
107
+ };
108
+ }
109
+
110
+ export { buildFaqPageJsonLd, createSeoBuilders, toPlainTextFromFaqBlocks };
@@ -0,0 +1,15 @@
1
+ // src/utils/optional-import.ts
2
+ async function importOptional(specifier) {
3
+ try {
4
+ return await import(
5
+ /* webpackIgnore: true */
6
+ specifier
7
+ );
8
+ } catch {
9
+ throw new Error(
10
+ `"${specifier}" is required for this provider but is not installed. Install it with: npm install ${specifier}`
11
+ );
12
+ }
13
+ }
14
+
15
+ export { importOptional };
@@ -0,0 +1,14 @@
1
+ import { bytesToHex } from './chunk-MXQ66RUN.js';
2
+ import 'server-only';
3
+
4
+ var CREDENTIAL_TAG_DOMAIN = "credential-tag-v1";
5
+ var NO_PASSWORD_TAG = "no-password";
6
+ async function buildCredentialTag(passwordHash, _revocationSeed) {
7
+ if (!passwordHash) return NO_PASSWORD_TAG;
8
+ const payload = `${CREDENTIAL_TAG_DOMAIN}:${passwordHash}`;
9
+ const data = new TextEncoder().encode(payload);
10
+ const digest = await crypto.subtle.digest("SHA-256", data);
11
+ return bytesToHex(new Uint8Array(digest));
12
+ }
13
+
14
+ export { NO_PASSWORD_TAG, buildCredentialTag };
@@ -0,0 +1,109 @@
1
+ import { importOptional } from './chunk-CTYAVMOF.js';
2
+
3
+ // src/email/index.ts
4
+ function createSendGridProvider(appName) {
5
+ return {
6
+ async send(params) {
7
+ const sgMail = await importOptional("@sendgrid/mail");
8
+ const apiKey = process.env.SENDGRID_API_KEY;
9
+ const fromEmail = process.env.SENDGRID_FROM_EMAIL;
10
+ if (!apiKey) throw new Error("SENDGRID_API_KEY is not set");
11
+ if (!fromEmail) throw new Error("SENDGRID_FROM_EMAIL is not set");
12
+ sgMail.default.setApiKey(apiKey);
13
+ const msg = {
14
+ to: params.to,
15
+ from: { email: fromEmail, name: appName },
16
+ subject: params.subject,
17
+ text: params.text ?? params.html.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim(),
18
+ html: params.html,
19
+ mailSettings: {
20
+ bypassListManagement: { enable: true },
21
+ sandboxMode: { enable: false }
22
+ },
23
+ trackingSettings: {
24
+ clickTracking: { enable: false },
25
+ openTracking: { enable: false },
26
+ subscriptionTracking: { enable: false }
27
+ }
28
+ };
29
+ try {
30
+ await sgMail.default.send(msg);
31
+ } catch (error) {
32
+ console.error("Error sending email:", error);
33
+ const emailError = error;
34
+ if (emailError.response) {
35
+ console.error("Email error details:", emailError.response.body.errors);
36
+ }
37
+ throw error;
38
+ }
39
+ }
40
+ };
41
+ }
42
+ function createResendProvider(appName) {
43
+ return {
44
+ async send(params) {
45
+ const { Resend } = await importOptional("resend");
46
+ const apiKey = process.env.RESEND_API_KEY;
47
+ const fromEmail = process.env.RESEND_FROM_EMAIL;
48
+ if (!apiKey) throw new Error("RESEND_API_KEY is not set");
49
+ if (!fromEmail) throw new Error("RESEND_FROM_EMAIL is not set");
50
+ const resend = new Resend(apiKey);
51
+ await resend.emails.send({
52
+ from: `${appName} <${fromEmail}>`,
53
+ to: params.to,
54
+ subject: params.subject,
55
+ html: params.html,
56
+ text: params.text
57
+ });
58
+ }
59
+ };
60
+ }
61
+ function extractUrls(text) {
62
+ const urlPattern = /https?:\/\/[^\s<>"')\]]+/g;
63
+ return [...new Set(text.match(urlPattern) ?? [])];
64
+ }
65
+ function formatConsoleEmail(params) {
66
+ const body = params.text ?? params.html.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
67
+ const urls = extractUrls(params.text ?? params.html);
68
+ const separator = "-".repeat(60);
69
+ const lines = [
70
+ separator,
71
+ " EMAIL (console mode)",
72
+ ` To: ${params.to}`,
73
+ ` Subject: ${params.subject}`
74
+ ];
75
+ if (urls.length > 0) {
76
+ lines.push("");
77
+ lines.push(urls.length === 1 ? " Action link:" : " Links:");
78
+ for (const url of urls) {
79
+ lines.push(` ${url}`);
80
+ }
81
+ }
82
+ const preview = body.length > 200 ? body.slice(0, 200) + "..." : body;
83
+ lines.push("");
84
+ lines.push(` Body: ${preview}`);
85
+ lines.push(separator);
86
+ return lines.join("\n");
87
+ }
88
+ function createConsoleProvider() {
89
+ return {
90
+ async send(params) {
91
+ console.log(formatConsoleEmail(params));
92
+ }
93
+ };
94
+ }
95
+ var providerFactories = {
96
+ sendgrid: createSendGridProvider,
97
+ resend: createResendProvider,
98
+ console: () => createConsoleProvider()
99
+ };
100
+ function createEmailService(config) {
101
+ const factory = providerFactories[config.provider] ?? providerFactories.console;
102
+ const provider = factory(config.appName);
103
+ async function sendEmail(params) {
104
+ return provider.send(params);
105
+ }
106
+ return { sendEmail };
107
+ }
108
+
109
+ export { createEmailService, formatConsoleEmail };
@@ -0,0 +1,28 @@
1
+ import 'server-only';
2
+
3
+ // src/auth/crypto-utils.ts
4
+ function constantTimeEqual(a, b) {
5
+ const maxLen = Math.max(a.length, b.length);
6
+ let result = 0;
7
+ for (let i = 0; i < maxLen; i++) {
8
+ const ac = i < a.length ? a.charCodeAt(i) : 0;
9
+ const bc = i < b.length ? b.charCodeAt(i) : 0;
10
+ result |= ac ^ bc;
11
+ }
12
+ result |= a.length ^ b.length;
13
+ return result === 0;
14
+ }
15
+ function bytesToHex(bytes) {
16
+ return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
17
+ }
18
+ function escapeHtml(str) {
19
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
20
+ }
21
+ async function hashToken(token, domain) {
22
+ const payload = `${domain}:${token}`;
23
+ const data = new TextEncoder().encode(payload);
24
+ const digest = await crypto.subtle.digest("SHA-256", data);
25
+ return bytesToHex(new Uint8Array(digest));
26
+ }
27
+
28
+ export { bytesToHex, constantTimeEqual, escapeHtml, hashToken };
@@ -0,0 +1,149 @@
1
+ import { constantTimeEqual, bytesToHex } from './chunk-MXQ66RUN.js';
2
+ import 'server-only';
3
+ import { cookies } from 'next/headers';
4
+
5
+ var CSRF_TOKEN_NAME = "csrf-token";
6
+ var CSRF_HEADER_NAME = "x-csrf-token";
7
+ var CSRF_KEY_DOMAIN_SEPARATOR = "csrf-protection-key-v1";
8
+ function stringToUint8Array(str) {
9
+ return new TextEncoder().encode(str);
10
+ }
11
+ function generateRandomBytes(length) {
12
+ const array = new Uint8Array(length);
13
+ crypto.getRandomValues(array);
14
+ return bytesToHex(array);
15
+ }
16
+ async function createHMAC(key, data) {
17
+ const keyData = stringToUint8Array(key);
18
+ const messageData = stringToUint8Array(data);
19
+ const cryptoKey = await crypto.subtle.importKey(
20
+ "raw",
21
+ keyData.buffer,
22
+ { name: "HMAC", hash: "SHA-256" },
23
+ false,
24
+ ["sign"]
25
+ );
26
+ const signature = await crypto.subtle.sign("HMAC", cryptoKey, messageData.buffer);
27
+ return bytesToHex(new Uint8Array(signature));
28
+ }
29
+ function createCSRFProtection(config) {
30
+ let _csrfDerivedKey = null;
31
+ async function getCSRFSecret() {
32
+ if (_csrfDerivedKey) return _csrfDerivedKey;
33
+ const jwtSecret = config.getJWTSecret();
34
+ _csrfDerivedKey = await createHMAC(jwtSecret, CSRF_KEY_DOMAIN_SEPARATOR);
35
+ return _csrfDerivedKey;
36
+ }
37
+ async function generateCSRFToken(sessionFingerprint) {
38
+ const timestamp = Date.now();
39
+ const randomToken = generateRandomBytes(32);
40
+ const payload = sessionFingerprint ? `${randomToken}.${timestamp}.${sessionFingerprint}` : `${randomToken}.${timestamp}`;
41
+ const secret = await getCSRFSecret();
42
+ const signature = await createHMAC(secret, payload);
43
+ return `${payload}.${signature}`;
44
+ }
45
+ async function verifyCSRFToken(token, expectedFingerprint) {
46
+ if (!token) return false;
47
+ try {
48
+ const parts = token.split(".");
49
+ const isBound = expectedFingerprint !== void 0 && expectedFingerprint !== "";
50
+ if (isBound) {
51
+ if (parts.length !== 4) return false;
52
+ const [randomToken2, timestamp2, fingerprint, signature2] = parts;
53
+ if (!constantTimeEqual(fingerprint, expectedFingerprint)) return false;
54
+ const payload2 = `${randomToken2}.${timestamp2}.${fingerprint}`;
55
+ const secret2 = await getCSRFSecret();
56
+ const expectedSignature2 = await createHMAC(secret2, payload2);
57
+ if (!constantTimeEqual(signature2, expectedSignature2)) return false;
58
+ const tokenAge2 = Date.now() - parseInt(timestamp2, 10);
59
+ if (tokenAge2 > 60 * 60 * 1e3) return false;
60
+ return true;
61
+ }
62
+ if (parts.length !== 3) return false;
63
+ const [randomToken, timestamp, signature] = parts;
64
+ const payload = `${randomToken}.${timestamp}`;
65
+ const secret = await getCSRFSecret();
66
+ const expectedSignature = await createHMAC(secret, payload);
67
+ if (!constantTimeEqual(signature, expectedSignature)) return false;
68
+ const tokenAge = Date.now() - parseInt(timestamp, 10);
69
+ if (tokenAge > 60 * 60 * 1e3) return false;
70
+ return true;
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+ async function requireCSRFToken(sessionFingerprint) {
76
+ const cookieStore = await cookies();
77
+ const existingToken = cookieStore.get(CSRF_TOKEN_NAME)?.value;
78
+ if (existingToken && await verifyCSRFToken(existingToken, sessionFingerprint)) {
79
+ return existingToken;
80
+ }
81
+ const newToken = await generateCSRFToken(sessionFingerprint);
82
+ cookieStore.set(CSRF_TOKEN_NAME, newToken, {
83
+ httpOnly: true,
84
+ secure: process.env.NODE_ENV === "production",
85
+ sameSite: "strict",
86
+ maxAge: 60 * 60,
87
+ path: "/"
88
+ });
89
+ return newToken;
90
+ }
91
+ function validateOrigin(request) {
92
+ const origin = request.headers.get("origin");
93
+ const host = request.headers.get("host");
94
+ if (!origin || !host) return false;
95
+ try {
96
+ const originUrl = new URL(origin);
97
+ return originUrl.host === host;
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
102
+ function getClientInfo(request) {
103
+ const forwarded = request.headers.get("x-forwarded-for");
104
+ const realIp = request.headers.get("x-real-ip");
105
+ const ipAddress = forwarded?.split(",")[0] || realIp || "unknown";
106
+ const userAgent = request.headers.get("user-agent") || "unknown";
107
+ return { ipAddress, userAgent };
108
+ }
109
+ async function validateCSRFRequest(request, sessionFingerprint) {
110
+ if (!["POST", "PUT", "DELETE", "PATCH"].includes(request.method)) return true;
111
+ const { ipAddress, userAgent } = getClientInfo(request);
112
+ const endpoint = request.nextUrl.pathname;
113
+ if (!validateOrigin(request)) {
114
+ config.logViolation({
115
+ ipAddress,
116
+ userAgent,
117
+ endpoint: `${endpoint} - Origin validation failed`
118
+ });
119
+ return false;
120
+ }
121
+ const headerToken = request.headers.get(CSRF_HEADER_NAME);
122
+ if (!headerToken) {
123
+ config.logViolation({
124
+ ipAddress,
125
+ userAgent,
126
+ endpoint: `${endpoint} - Missing CSRF token`
127
+ });
128
+ return false;
129
+ }
130
+ const isValid = await verifyCSRFToken(headerToken, sessionFingerprint);
131
+ if (!isValid) {
132
+ config.logViolation({
133
+ ipAddress,
134
+ userAgent,
135
+ endpoint: `${endpoint} - Invalid CSRF token`
136
+ });
137
+ return false;
138
+ }
139
+ return true;
140
+ }
141
+ return {
142
+ generateCSRFToken,
143
+ verifyCSRFToken,
144
+ requireCSRFToken,
145
+ validateCSRFRequest
146
+ };
147
+ }
148
+
149
+ export { createCSRFProtection };