nextly 0.0.1 → 0.0.2-alpha.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 (268) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +122 -0
  3. package/dist/_dts-chunks/collections-handler.d-DjgO74Wt.d.ts +20540 -0
  4. package/dist/_dts-chunks/config.d-DNwsDnjs.d.ts +2589 -0
  5. package/dist/_dts-chunks/define-component.d-BUgTHmt3.d.ts +1149 -0
  6. package/dist/_dts-chunks/image-processor.d-OO1PmMrv.d.ts +335 -0
  7. package/dist/_dts-chunks/index.d-axCAzZ7m.d.ts +17842 -0
  8. package/dist/_dts-chunks/media.d-DjDOZo4B.d.ts +117 -0
  9. package/dist/_dts-chunks/on-error.d-CHIKWNxd.d.ts +38 -0
  10. package/dist/_dts-chunks/storage.d-BUhQ2we_.d.ts +404 -0
  11. package/dist/actions/index.d.ts +239 -0
  12. package/dist/actions/index.mjs +281 -0
  13. package/dist/api/auth-state.d.ts +5 -0
  14. package/dist/api/auth-state.mjs +131 -0
  15. package/dist/api/collections-schema-detail.d.ts +56 -0
  16. package/dist/api/collections-schema-detail.mjs +244 -0
  17. package/dist/api/collections-schema-export.d.ts +56 -0
  18. package/dist/api/collections-schema-export.mjs +129 -0
  19. package/dist/api/collections-schema.d.ts +59 -0
  20. package/dist/api/collections-schema.mjs +207 -0
  21. package/dist/api/components-detail.d.ts +50 -0
  22. package/dist/api/components-detail.mjs +132 -0
  23. package/dist/api/components.d.ts +69 -0
  24. package/dist/api/components.mjs +144 -0
  25. package/dist/api/email-providers-default.d.ts +40 -0
  26. package/dist/api/email-providers-default.mjs +75 -0
  27. package/dist/api/email-providers-detail.d.ts +81 -0
  28. package/dist/api/email-providers-detail.mjs +109 -0
  29. package/dist/api/email-providers-test.d.ts +43 -0
  30. package/dist/api/email-providers-test.mjs +114 -0
  31. package/dist/api/email-providers.d.ts +69 -0
  32. package/dist/api/email-providers.mjs +110 -0
  33. package/dist/api/email-send-template.d.ts +41 -0
  34. package/dist/api/email-send-template.mjs +58 -0
  35. package/dist/api/email-send.d.ts +42 -0
  36. package/dist/api/email-send.mjs +58 -0
  37. package/dist/api/email-templates-detail.d.ts +74 -0
  38. package/dist/api/email-templates-detail.mjs +112 -0
  39. package/dist/api/email-templates-layout.d.ts +55 -0
  40. package/dist/api/email-templates-layout.mjs +92 -0
  41. package/dist/api/email-templates-preview.d.ts +48 -0
  42. package/dist/api/email-templates-preview.mjs +93 -0
  43. package/dist/api/email-templates.d.ts +61 -0
  44. package/dist/api/email-templates.mjs +118 -0
  45. package/dist/api/health.d.ts +68 -0
  46. package/dist/api/health.mjs +67 -0
  47. package/dist/api/index.d.ts +54 -0
  48. package/dist/api/index.mjs +16 -0
  49. package/dist/api/media-bulk.d.ts +74 -0
  50. package/dist/api/media-bulk.mjs +196 -0
  51. package/dist/api/media-folders.d.ts +112 -0
  52. package/dist/api/media-folders.mjs +187 -0
  53. package/dist/api/media-handlers.d.ts +102 -0
  54. package/dist/api/media-handlers.mjs +437 -0
  55. package/dist/api/media.d.ts +117 -0
  56. package/dist/api/media.mjs +242 -0
  57. package/dist/api/singles-detail.d.ts +87 -0
  58. package/dist/api/singles-detail.mjs +170 -0
  59. package/dist/api/singles-schema-detail.d.ts +54 -0
  60. package/dist/api/singles-schema-detail.mjs +182 -0
  61. package/dist/api/singles.d.ts +34 -0
  62. package/dist/api/singles.mjs +94 -0
  63. package/dist/api/storage-upload-url.d.ts +48 -0
  64. package/dist/api/storage-upload-url.mjs +202 -0
  65. package/dist/api/uploads.d.ts +109 -0
  66. package/dist/api/uploads.mjs +359 -0
  67. package/dist/auth/index.d.ts +425 -0
  68. package/dist/auth/index.mjs +199 -0
  69. package/dist/boot-apply-PQSYLDIN.mjs +7 -0
  70. package/dist/chunk-2OALJTK6.mjs +489 -0
  71. package/dist/chunk-2Q2SX2CS.mjs +365 -0
  72. package/dist/chunk-2TFX4ND3.mjs +13 -0
  73. package/dist/chunk-2TWPDSYD.mjs +87 -0
  74. package/dist/chunk-2W3DVD7S.mjs +647 -0
  75. package/dist/chunk-2ZFKXPQM.mjs +88 -0
  76. package/dist/chunk-3FA7FKAV.mjs +832 -0
  77. package/dist/chunk-3NZ2KMBL.mjs +58 -0
  78. package/dist/chunk-4MJLT6PZ.mjs +0 -0
  79. package/dist/chunk-56WO4WX7.mjs +0 -0
  80. package/dist/chunk-5APFUGAD.mjs +89 -0
  81. package/dist/chunk-5HMZ644B.mjs +108 -0
  82. package/dist/chunk-67GXH6PR.mjs +32 -0
  83. package/dist/chunk-6JNEPWRW.mjs +14368 -0
  84. package/dist/chunk-6NFHQIJD.mjs +45 -0
  85. package/dist/chunk-7P6ASYW6.mjs +9 -0
  86. package/dist/chunk-A3WPLSDT.mjs +1364 -0
  87. package/dist/chunk-AGJ6F2T3.mjs +144 -0
  88. package/dist/chunk-AK6Z23OX.mjs +1464 -0
  89. package/dist/chunk-APKKRD2G.mjs +102 -0
  90. package/dist/chunk-B2GV2BWH.mjs +73 -0
  91. package/dist/chunk-D5HQBNUB.mjs +74 -0
  92. package/dist/chunk-DNNG377Z.mjs +204 -0
  93. package/dist/chunk-DP3G27G5.mjs +135 -0
  94. package/dist/chunk-DV6WVX2Q.mjs +0 -0
  95. package/dist/chunk-DXGGXIUZ.mjs +57 -0
  96. package/dist/chunk-EGXBZCGC.mjs +943 -0
  97. package/dist/chunk-ERCNLX3V.mjs +176 -0
  98. package/dist/chunk-FQULBZ53.mjs +850 -0
  99. package/dist/chunk-G2AA4QLC.mjs +262 -0
  100. package/dist/chunk-GDBJ5JCU.mjs +488 -0
  101. package/dist/chunk-GJNSJU4S.mjs +19 -0
  102. package/dist/chunk-GZ6DCQKC.mjs +69 -0
  103. package/dist/chunk-H26B4FYG.mjs +167 -0
  104. package/dist/chunk-I4JMR3UR.mjs +21 -0
  105. package/dist/chunk-INV7QKLG.mjs +508 -0
  106. package/dist/chunk-IUDOC7N7.mjs +46 -0
  107. package/dist/chunk-IZWPRDC3.mjs +206 -0
  108. package/dist/chunk-KIMNCZGV.mjs +15 -0
  109. package/dist/chunk-L6HW2DA7.mjs +15 -0
  110. package/dist/chunk-LAZXX4HR.mjs +100 -0
  111. package/dist/chunk-LDKCUMHK.mjs +95 -0
  112. package/dist/chunk-LRXMECUA.mjs +0 -0
  113. package/dist/chunk-M52VMPGA.mjs +119 -0
  114. package/dist/chunk-MGUWEEI6.mjs +160 -0
  115. package/dist/chunk-NRUWQ5Z7.mjs +419 -0
  116. package/dist/chunk-NSEFNNU4.mjs +25360 -0
  117. package/dist/chunk-NTHVDFGO.mjs +138 -0
  118. package/dist/chunk-O3QHXMOX.mjs +3166 -0
  119. package/dist/chunk-P7NH2OSC.mjs +2605 -0
  120. package/dist/chunk-PKMABBB5.mjs +184 -0
  121. package/dist/chunk-PWS6XGJK.mjs +76 -0
  122. package/dist/chunk-R6JJQHFC.mjs +20 -0
  123. package/dist/chunk-RJLLGGPG.mjs +0 -0
  124. package/dist/chunk-SBACDPNX.mjs +689 -0
  125. package/dist/chunk-TO5AFLVQ.mjs +124 -0
  126. package/dist/chunk-TS7GHTG2.mjs +5436 -0
  127. package/dist/chunk-UJ2IMJ4W.mjs +133 -0
  128. package/dist/chunk-UOP63Q54.mjs +102 -0
  129. package/dist/chunk-UUOFWCM6.mjs +78 -0
  130. package/dist/chunk-V4EQTOA4.mjs +893 -0
  131. package/dist/chunk-VJ66NCL4.mjs +193 -0
  132. package/dist/chunk-VQJQHVEV.mjs +29 -0
  133. package/dist/chunk-VTJADRO3.mjs +141 -0
  134. package/dist/chunk-VWF3JO32.mjs +0 -0
  135. package/dist/chunk-W4MGXIRR.mjs +27 -0
  136. package/dist/chunk-W5KKPZT5.mjs +1204 -0
  137. package/dist/chunk-WD34YQ6T.mjs +381 -0
  138. package/dist/chunk-WZBYMYVW.mjs +14 -0
  139. package/dist/chunk-X23WKS3Z.mjs +50 -0
  140. package/dist/chunk-X7TXCYYN.mjs +6496 -0
  141. package/dist/chunk-XGI4EMS3.mjs +140 -0
  142. package/dist/chunk-XZKLBMN6.mjs +1153 -0
  143. package/dist/chunk-YB7INWPY.mjs +0 -0
  144. package/dist/chunk-YV4Y7SDL.mjs +83 -0
  145. package/dist/chunk-YZNBLFIW.mjs +1688 -0
  146. package/dist/chunk-YZZCTONM.mjs +263 -0
  147. package/dist/chunk-ZE6A3FYH.mjs +289 -0
  148. package/dist/cli/nextly.mjs +68 -0
  149. package/dist/cli/utils/index.d.ts +449 -0
  150. package/dist/cli/utils/index.mjs +49 -0
  151. package/dist/component-schema-service-5577KVW6.mjs +11 -0
  152. package/dist/config-loader-23YEMC3Z.mjs +23 -0
  153. package/dist/config.d.ts +44 -0
  154. package/dist/config.mjs +109 -0
  155. package/dist/container-ORGFGYSZ.mjs +9 -0
  156. package/dist/database/index.d.ts +12 -0
  157. package/dist/database/index.mjs +40 -0
  158. package/dist/database/seeders/index.d.ts +93 -0
  159. package/dist/database/seeders/index.mjs +47 -0
  160. package/dist/db-sync-demote-LJGKLB3S.mjs +117 -0
  161. package/dist/db-sync-promote-B26VSYQF.mjs +113 -0
  162. package/dist/dev-reload-broadcaster-B73IQ53V.mjs +25 -0
  163. package/dist/dist-M2NOU37V.mjs +19 -0
  164. package/dist/drizzle-kit-lazy-D2M2PXR2.mjs +13 -0
  165. package/dist/dynamic-collection-schema-service-IEXTPIZ7.mjs +8 -0
  166. package/dist/errors/index.d.ts +159 -0
  167. package/dist/errors/index.mjs +10 -0
  168. package/dist/factory-IWMBKUJM.mjs +15 -0
  169. package/dist/first-run-QIVKWJIF.mjs +63 -0
  170. package/dist/fresh-push-NR67DC3R.mjs +8 -0
  171. package/dist/index.d.ts +4175 -0
  172. package/dist/index.mjs +1336 -0
  173. package/dist/local-plugin-PTET4NAT.mjs +7 -0
  174. package/dist/logger-NU46DXNY.mjs +15 -0
  175. package/dist/logger-YE4TC7ZN.mjs +9 -0
  176. package/dist/migration-journal-EP532Y4L.mjs +139 -0
  177. package/dist/migrations/mysql/0000_eager_sentry.sql +174 -0
  178. package/dist/migrations/mysql/0001_soft_giant_girl.sql +27 -0
  179. package/dist/migrations/mysql/0002_media_table.sql +24 -0
  180. package/dist/migrations/mysql/0003_dynamic_singles.sql +37 -0
  181. package/dist/migrations/mysql/0004_dynamic_components.sql +35 -0
  182. package/dist/migrations/mysql/0005_user_management_tables.sql +92 -0
  183. package/dist/migrations/mysql/0006_api_keys.sql +36 -0
  184. package/dist/migrations/mysql/0007_general_settings.sql +20 -0
  185. package/dist/migrations/mysql/0008_site_settings_logo_url.sql +9 -0
  186. package/dist/migrations/mysql/0009_activity_log.sql +30 -0
  187. package/dist/migrations/mysql/0010_site_settings_sidebar.sql +13 -0
  188. package/dist/migrations/mysql/0011_missing_tables_and_columns.sql +54 -0
  189. package/dist/migrations/mysql/0012_image_sizes_and_focal_point.sql +30 -0
  190. package/dist/migrations/mysql/0012_media_folders.sql +43 -0
  191. package/dist/migrations/mysql/0013_user_brute_force_protection.sql +31 -0
  192. package/dist/migrations/mysql/0014_email_template_attachments.sql +12 -0
  193. package/dist/migrations/mysql/0015_media_uploaded_by_nullable.sql +15 -0
  194. package/dist/migrations/mysql/20260429_000000_000_initial_journal.sql +22 -0
  195. package/dist/migrations/mysql/20260501_000000_journal_batch.sql +17 -0
  196. package/dist/migrations/mysql/20260501_000001_audit_log.sql +24 -0
  197. package/dist/migrations/mysql/20260504_000000_nextly_meta.sql +21 -0
  198. package/dist/migrations/mysql/meta/0000_snapshot.json +1005 -0
  199. package/dist/migrations/mysql/meta/0001_snapshot.json +1099 -0
  200. package/dist/migrations/mysql/meta/_journal.json +41 -0
  201. package/dist/migrations/postgresql/0000_misty_king_bedlam.sql +169 -0
  202. package/dist/migrations/postgresql/0001_perpetual_captain_marvel.sql +8 -0
  203. package/dist/migrations/postgresql/0002_sad_spectrum.sql +16 -0
  204. package/dist/migrations/postgresql/0003_hesitant_ultron.sql +17 -0
  205. package/dist/migrations/postgresql/0004_media_table.sql +24 -0
  206. package/dist/migrations/postgresql/0005_media_folders.sql +36 -0
  207. package/dist/migrations/postgresql/0006_dynamic_collections_update.sql +50 -0
  208. package/dist/migrations/postgresql/0007_dynamic_singles.sql +38 -0
  209. package/dist/migrations/postgresql/0008_dynamic_components.sql +37 -0
  210. package/dist/migrations/postgresql/0009_user_management_tables.sql +95 -0
  211. package/dist/migrations/postgresql/0010_api_keys.sql +34 -0
  212. package/dist/migrations/postgresql/0011_general_settings.sql +20 -0
  213. package/dist/migrations/postgresql/0012_site_settings_logo_url.sql +9 -0
  214. package/dist/migrations/postgresql/0013_activity_log.sql +29 -0
  215. package/dist/migrations/postgresql/0014_image_sizes_and_focal_point.sql +33 -0
  216. package/dist/migrations/postgresql/0014_site_settings_sidebar.sql +13 -0
  217. package/dist/migrations/postgresql/0015_user_brute_force_protection.sql +29 -0
  218. package/dist/migrations/postgresql/0016_email_template_attachments.sql +12 -0
  219. package/dist/migrations/postgresql/0017_media_uploaded_by_nullable.sql +15 -0
  220. package/dist/migrations/postgresql/20260429_000000_000_initial_journal.sql +24 -0
  221. package/dist/migrations/postgresql/20260501_000000_journal_batch.sql +17 -0
  222. package/dist/migrations/postgresql/20260501_000001_audit_log.sql +24 -0
  223. package/dist/migrations/postgresql/20260504_000000_nextly_meta.sql +22 -0
  224. package/dist/migrations/postgresql/meta/0000_snapshot.json +1286 -0
  225. package/dist/migrations/postgresql/meta/0001_snapshot.json +1407 -0
  226. package/dist/migrations/postgresql/meta/0002_snapshot.json +1552 -0
  227. package/dist/migrations/postgresql/meta/0003_snapshot.json +1695 -0
  228. package/dist/migrations/postgresql/meta/0010_snapshot.json +2345 -0
  229. package/dist/migrations/postgresql/meta/_journal.json +90 -0
  230. package/dist/migrations/sqlite/0000_api_keys.sql +34 -0
  231. package/dist/migrations/sqlite/0001_general_settings.sql +20 -0
  232. package/dist/migrations/sqlite/0002_site_settings_logo_url.sql +9 -0
  233. package/dist/migrations/sqlite/0003_activity_log.sql +29 -0
  234. package/dist/migrations/sqlite/0004_image_sizes_and_focal_point.sql +29 -0
  235. package/dist/migrations/sqlite/0004_site_settings_sidebar.sql +11 -0
  236. package/dist/migrations/sqlite/0005_user_brute_force_protection.sql +29 -0
  237. package/dist/migrations/sqlite/0006_email_template_attachments.sql +12 -0
  238. package/dist/migrations/sqlite/0007_media_uploaded_by_nullable.sql +111 -0
  239. package/dist/migrations/sqlite/20260429_000000_000_initial_journal.sql +24 -0
  240. package/dist/migrations/sqlite/20260501_000000_journal_batch.sql +19 -0
  241. package/dist/migrations/sqlite/20260501_000001_audit_log.sql +24 -0
  242. package/dist/migrations/sqlite/20260504_000000_nextly_meta.sql +21 -0
  243. package/dist/migrations/sqlite/20260505_000000_user_management_tables.sql +77 -0
  244. package/dist/next.d.ts +57 -0
  245. package/dist/next.mjs +55 -0
  246. package/dist/observability/index.d.ts +87 -0
  247. package/dist/observability/index.mjs +57 -0
  248. package/dist/permissions-3DZZQZMI.mjs +39 -0
  249. package/dist/pipeline-YOML7SWF.mjs +29 -0
  250. package/dist/preview-ZZTR3QGS.mjs +9 -0
  251. package/dist/program-PW6UB2ZC.mjs +5934 -0
  252. package/dist/reconcile-single-tables-7ENVXJGB.mjs +7 -0
  253. package/dist/register-SF6E6FVU.mjs +49 -0
  254. package/dist/reload-config-HWQ4G5MM.mjs +23 -0
  255. package/dist/resolve-single-table-name-JSOMUB3R.mjs +7 -0
  256. package/dist/routeHandler-UNMMJIBM.mjs +77 -0
  257. package/dist/runtime-schema-generator-NRA6A6Z6.mjs +8 -0
  258. package/dist/runtime.d.ts +120 -0
  259. package/dist/runtime.mjs +73 -0
  260. package/dist/schema-hash-FMMG6VPJ.mjs +13 -0
  261. package/dist/schema-registry-EQ36FZDP.mjs +7 -0
  262. package/dist/scripts/load-env.mjs +42 -0
  263. package/dist/storage/index.d.ts +566 -0
  264. package/dist/storage/index.mjs +45 -0
  265. package/dist/super-admin-G5ZK5F4T.mjs +39 -0
  266. package/dist/system-table-service-WGSRVEGT.mjs +17 -0
  267. package/dist/users-7KELGRYJ.mjs +38 -0
  268. package/package.json +308 -9
@@ -0,0 +1,425 @@
1
+ /**
2
+ * Convert a string secret to a Uint8Array key for jose.
3
+ * Uses the raw bytes of the secret (UTF-8 encoded).
4
+ */
5
+ declare function secretToKey(secret: string): Uint8Array;
6
+ /**
7
+ * Sign a JWT access token.
8
+ *
9
+ * @param claims - The payload claims (from buildClaims)
10
+ * @param secret - The NEXTLY_SECRET string
11
+ * @param ttlSeconds - Token TTL in seconds (default 900 = 15 minutes)
12
+ * @returns Signed JWT string
13
+ */
14
+ declare function signAccessToken(claims: Record<string, unknown>, secret: string, ttlSeconds?: number): Promise<string>;
15
+
16
+ interface NextlyJwtPayload {
17
+ sub: string;
18
+ iat: number;
19
+ exp: number;
20
+ jti: string;
21
+ email: string;
22
+ name: string;
23
+ image: string | null;
24
+ roleIds: string[];
25
+ [key: string]: unknown;
26
+ }
27
+ interface BuildClaimsInput {
28
+ userId: string;
29
+ email: string;
30
+ name: string;
31
+ image: string | null;
32
+ roleIds: string[];
33
+ customFields?: Record<string, unknown>;
34
+ }
35
+ /**
36
+ * Build JWT claims from user data.
37
+ * Does NOT set iat/exp/jti -- those are set by the signing function.
38
+ */
39
+ declare function buildClaims(input: BuildClaimsInput): Record<string, unknown>;
40
+
41
+ type VerifyResult = {
42
+ valid: true;
43
+ payload: NextlyJwtPayload;
44
+ } | {
45
+ valid: false;
46
+ reason: "expired" | "invalid" | "malformed";
47
+ };
48
+ /**
49
+ * Verify and decode a JWT access token.
50
+ *
51
+ * @param token - The JWT string from the cookie
52
+ * @param secret - The NEXTLY_SECRET string
53
+ * @returns VerifyResult with payload on success or reason on failure
54
+ */
55
+ declare function verifyAccessToken(token: string, secret: string): Promise<VerifyResult>;
56
+
57
+ interface SessionUser {
58
+ id: string;
59
+ email: string;
60
+ name: string;
61
+ image: string | null;
62
+ roleIds: string[];
63
+ /** Custom user fields from user_ext table */
64
+ [key: string]: unknown;
65
+ }
66
+ interface AuthContext {
67
+ userId: string;
68
+ userName: string;
69
+ userEmail: string;
70
+ permissions: Map<string, boolean>;
71
+ roles: string[];
72
+ authMethod: "session" | "api-key";
73
+ }
74
+ interface RefreshTokenRecord {
75
+ id: string;
76
+ userId: string;
77
+ tokenHash: string;
78
+ userAgent: string | null;
79
+ ipAddress: string | null;
80
+ expiresAt: Date;
81
+ createdAt: Date;
82
+ }
83
+
84
+ type GetSessionResult = {
85
+ authenticated: true;
86
+ user: SessionUser;
87
+ } | {
88
+ authenticated: false;
89
+ reason: "no_token" | "expired" | "invalid";
90
+ };
91
+ /**
92
+ * Extract and verify the session from a request.
93
+ * No database hit -- purely stateless JWT verification.
94
+ */
95
+ declare function getSession(request: Request, secret: string): Promise<GetSessionResult>;
96
+ /**
97
+ * Check if a session user has a specific role.
98
+ */
99
+ declare function hasRole(user: SessionUser, roleSlug: string): boolean;
100
+ /**
101
+ * Check if a session user has any of the specified roles.
102
+ */
103
+ declare function hasAnyRole(user: SessionUser, roleSlugs: string[]): boolean;
104
+ /**
105
+ * Check if a session user has all of the specified roles.
106
+ */
107
+ declare function hasAllRoles(user: SessionUser, roleSlugs: string[]): boolean;
108
+
109
+ /**
110
+ * Generate a new opaque refresh token (64 bytes, hex-encoded = 128 chars).
111
+ */
112
+ declare function generateRefreshToken(): string;
113
+ /**
114
+ * Hash a refresh token using SHA-256 for database storage.
115
+ * We use SHA-256 (not bcrypt) because refresh tokens have high entropy (64 bytes).
116
+ */
117
+ declare function hashRefreshToken(token: string): string;
118
+
119
+ /**
120
+ * Require a valid session. Returns the session user or throws
121
+ * `NextlyError.authRequired()`.
122
+ */
123
+ declare function requireAuth(request: Request, secret: string): Promise<SessionUser>;
124
+
125
+ /**
126
+ * Require a specific role. Throws `NextlyError.forbidden()` if missing.
127
+ *
128
+ * Public message comes from the factory ("You don't have permission to
129
+ * perform this action."). The required role identity is recorded in
130
+ * logContext only; the wire response never includes it (spec §13.7).
131
+ */
132
+ declare function requireRole(user: SessionUser, roleSlug: string): void;
133
+ /**
134
+ * Require any of the specified roles. Throws `NextlyError.forbidden()` if
135
+ * none match. The role list lives in logContext per spec §13.7;
136
+ * the wire response never includes it.
137
+ */
138
+ declare function requireAnyRole(user: SessionUser, roleSlugs: string[]): void;
139
+ /**
140
+ * Require all of the specified roles. Throws `NextlyError.forbidden()` if
141
+ * any are missing. The role list lives in logContext per spec §13.7.
142
+ */
143
+ declare function requireAllRoles(user: SessionUser, roleSlugs: string[]): void;
144
+
145
+ interface ErrorResponse {
146
+ success: false;
147
+ statusCode: number;
148
+ message: string;
149
+ error: string;
150
+ data: null;
151
+ headers?: Record<string, string>;
152
+ }
153
+ /**
154
+ * Create a JSON error response object.
155
+ */
156
+ declare function createErrorResponse(statusCode: number, message: string, error: string): ErrorResponse;
157
+ /**
158
+ * Create a JSON Response from an ErrorResponse.
159
+ */
160
+ declare function createJsonErrorResponse(errResp: ErrorResponse): Response;
161
+ /**
162
+ * Check if a value is an ErrorResponse.
163
+ */
164
+ declare function isErrorResponse(value: unknown): value is ErrorResponse;
165
+ /**
166
+ * Check a single permission against an AuthContext.
167
+ * For session auth: delegates to RBAC service.
168
+ * For API key auth: checks pre-resolved permissions map.
169
+ */
170
+ declare function checkPermission(context: AuthContext, action: string, resource: string, deps: {
171
+ checkUserPermission?: (userId: string, action: string, resource: string) => Promise<boolean>;
172
+ }): Promise<boolean>;
173
+
174
+ /**
175
+ * Attempt API key authentication from the Authorization header.
176
+ * Returns null if no Bearer token present (not an error -- caller should try session auth).
177
+ * Returns ErrorResponse if token is present but invalid/expired/rate-limited.
178
+ * Returns AuthContext on success.
179
+ */
180
+ declare function authenticateApiKey(request: Request, deps: {
181
+ validateApiKey: (rawKey: string) => Promise<{
182
+ valid: boolean;
183
+ userId?: string;
184
+ permissions?: Map<string, boolean>;
185
+ roles?: string[];
186
+ error?: string;
187
+ retryAfter?: number;
188
+ }>;
189
+ }): Promise<AuthContext | ErrorResponse | null>;
190
+
191
+ /**
192
+ * Check if a user has the super-admin role.
193
+ *
194
+ * Resolves the user's full role set (direct + inherited) and checks
195
+ * if any role has the `super-admin` slug. Results are cached in-memory
196
+ * for 60 seconds.
197
+ *
198
+ * @param userId - The user ID to check
199
+ * @returns `true` if the user has the super-admin role
200
+ *
201
+ * @example
202
+ * ```typescript
203
+ * if (await isSuperAdmin(userId)) {
204
+ * // Bypass all access checks
205
+ * }
206
+ * ```
207
+ */
208
+ declare function isSuperAdmin(userId: string): Promise<boolean>;
209
+
210
+ type CollectionOperation = "create" | "read" | "update" | "delete";
211
+ interface CollectionAccessDeps {
212
+ /** Check a user's permission via RBAC service */
213
+ checkUserPermission?: (userId: string, action: string, resource: string) => Promise<boolean>;
214
+ /** Evaluate code-defined access function from defineCollection */
215
+ evaluateCodeAccess?: (collectionSlug: string, operation: CollectionOperation, context: AuthContext) => Promise<boolean | null>;
216
+ }
217
+ /**
218
+ * Check if the auth context has access to a collection operation.
219
+ * Priority: super-admin bypass > code-defined access > database RBAC.
220
+ */
221
+ declare function checkCollectionAccess(context: AuthContext, collectionSlug: string, operation: CollectionOperation, deps: CollectionAccessDeps): Promise<boolean>;
222
+
223
+ declare function hashPassword(plain: string, saltRounds?: number): Promise<string>;
224
+ declare function verifyPassword(plain: string, hash: string): Promise<boolean>;
225
+ type PasswordStrengthResult = {
226
+ ok: true;
227
+ errors?: undefined;
228
+ } | {
229
+ ok: false;
230
+ errors: string[];
231
+ };
232
+ declare function validatePasswordStrength(password: string): PasswordStrengthResult;
233
+
234
+ /**
235
+ * Single entry point for security-sensitive event recording. Callers
236
+ * pass a structured event; the writer persists it to the `audit_log`
237
+ * table via the database adapter.
238
+ *
239
+ * Behaviour contract:
240
+ *
241
+ * - **Never throws.** Auth handlers must not fail-open or fail-closed
242
+ * because the audit table is unreachable. A DB failure logs a
243
+ * structured warning via `getNextlyLogger()` and the request
244
+ * continues.
245
+ * - **Append-only by application convention.** Operators are expected
246
+ * to revoke UPDATE / DELETE GRANTs on the table in production for
247
+ * stricter integrity. The writer never offers an update path.
248
+ * - **Metadata is opaque JSON.** The `metadata` field stays generic
249
+ * so we can extend coverage without a migration each time. Callers
250
+ * pass dialect-portable JSON-serialisable values only.
251
+ *
252
+ * Hash-chained tamper-evidence (each row signs (prev_hash, this_row))
253
+ * is intentionally deferred — under concurrent auth events the chain
254
+ * needs a lock-around-write that complicates the hot path. Operators
255
+ * who need cryptographic integrity right now should rely on DB-level
256
+ *
257
+ * @module domains/audit/audit-log-writer
258
+ * @since 1.0.0
259
+ */
260
+ type AuditEventKind = "csrf-failed" | "login-failed" | "password-changed" | "role-assigned" | "role-revoked" | "user-deleted";
261
+ interface AuditEvent {
262
+ kind: AuditEventKind;
263
+ /** The user performing the action; null when unauthenticated (failed login, failed CSRF). */
264
+ actorUserId?: string | null;
265
+ /** The user being acted on; null when not account-scoped. */
266
+ targetUserId?: string | null;
267
+ ipAddress?: string | null;
268
+ userAgent?: string | null;
269
+ /** JSON-serialisable details. Goes into the dialect's JSON column. */
270
+ metadata?: Record<string, unknown>;
271
+ }
272
+ interface AuditLogWriter {
273
+ write(event: AuditEvent): Promise<void>;
274
+ }
275
+
276
+ /**
277
+ * Combined dependency interface for all auth handlers.
278
+ * Defined as a standalone interface (not multi-extends) to avoid TS2320 conflicts
279
+ * where the same method name has different return types across handler deps.
280
+ * The route handler builds this from the DI container services and config.
281
+ */
282
+ interface AuthRouterDeps {
283
+ secret: string;
284
+ isProduction: boolean;
285
+ accessTokenTTL: number;
286
+ refreshTokenTTL: number;
287
+ maxLoginAttempts: number;
288
+ lockoutDurationSeconds: number;
289
+ loginStallTimeMs: number;
290
+ requireEmailVerification: boolean;
291
+ /**
292
+ * Spec §13.2 opt-in flag. When false (the spec default), email-conflict
293
+ * registrations silent-success with a generic message. When true, the
294
+ * handler returns 409 DUPLICATE so the user knows the email is in use
295
+ * (trades enumeration risk for UX).
296
+ */
297
+ revealRegistrationConflict: boolean;
298
+ /**
299
+ * Dev-only auto-login config (config.admin.devAutoLogin). Hard-blocked
300
+ * at runtime when isProduction is true. When set, the session handler
301
+ * issues a real session for the named user on the first /admin visit
302
+ * if no valid session is present. See packages/nextly/src/auth/handlers/session.ts.
303
+ */
304
+ devAutoLogin: false | {
305
+ email: string;
306
+ password?: string;
307
+ };
308
+ allowedOrigins: string[];
309
+ /**
310
+ * When true, client-IP resolution honors `X-Forwarded-For` (filtered
311
+ * through `trustedProxyIps`). When false (default), proxy headers are
312
+ * ignored.
313
+ */
314
+ trustProxy: boolean;
315
+ /** CIDR list of proxy IPs (from TRUSTED_PROXY_IPS). */
316
+ trustedProxyIps: string[];
317
+ /**
318
+ * Per-IP rate limit on auth write endpoints. `requestsPerHour: 0`
319
+ * disables the envelope. Single shared bucket across login / register
320
+ * / forgot-password / reset-password per IP.
321
+ */
322
+ authRateLimit: {
323
+ requestsPerHour: number;
324
+ windowMs: number;
325
+ };
326
+ /**
327
+ * Writer for security-sensitive auth events. Handlers call
328
+ * `auditLog.write(...)` on failed CSRF, failed login, password change,
329
+ * role mutation, and user delete. Writer is fail-safe; any DB error
330
+ * logs a warning and the request continues.
331
+ */
332
+ auditLog: AuditLogWriter;
333
+ findUserByEmail: (email: string) => Promise<{
334
+ id: string;
335
+ email: string;
336
+ name: string;
337
+ image: string | null;
338
+ passwordHash: string;
339
+ emailVerified: Date | null;
340
+ isActive: boolean;
341
+ failedLoginAttempts: number;
342
+ lockedUntil: Date | null;
343
+ } | null>;
344
+ findUserById: (userId: string) => Promise<{
345
+ id: string;
346
+ email: string;
347
+ name: string;
348
+ image: string | null;
349
+ isActive: boolean;
350
+ } | null>;
351
+ incrementFailedAttempts: (userId: string) => Promise<void>;
352
+ lockAccount: (userId: string, lockedUntil: Date) => Promise<void>;
353
+ resetFailedAttempts: (userId: string) => Promise<void>;
354
+ fetchRoleIds: (userId: string) => Promise<string[]>;
355
+ fetchCustomFields: (userId: string) => Promise<Record<string, unknown>>;
356
+ storeRefreshToken: (record: {
357
+ id: string;
358
+ userId: string;
359
+ tokenHash: string;
360
+ userAgent: string | null;
361
+ ipAddress: string | null;
362
+ expiresAt: Date;
363
+ }) => Promise<void>;
364
+ findRefreshTokenByHash: (tokenHash: string) => Promise<{
365
+ id: string;
366
+ userId: string;
367
+ expiresAt: Date;
368
+ userAgent: string | null;
369
+ ipAddress: string | null;
370
+ } | null>;
371
+ deleteRefreshToken: (id: string) => Promise<void>;
372
+ deleteRefreshTokenByHash: (tokenHash: string) => Promise<void>;
373
+ deleteAllRefreshTokensForUser: (userId: string) => Promise<void>;
374
+ getUserCount: () => Promise<number>;
375
+ createSuperAdmin: (data: {
376
+ email: string;
377
+ name: string;
378
+ password: string;
379
+ }) => Promise<{
380
+ id: string;
381
+ email: string;
382
+ name: string;
383
+ }>;
384
+ seedPermissions: () => Promise<void>;
385
+ registerUser: (data: {
386
+ email: string;
387
+ password: string;
388
+ name: string;
389
+ }) => Promise<{
390
+ id: string;
391
+ email: string;
392
+ name: string | null;
393
+ }>;
394
+ generatePasswordResetToken: (email: string, redirectPath?: string) => Promise<{
395
+ token?: string;
396
+ }>;
397
+ resetPasswordWithToken: (token: string, newPassword: string) => Promise<{
398
+ email: string;
399
+ }>;
400
+ changePassword: (userId: string, currentPassword: string, newPassword: string) => Promise<{
401
+ success: boolean;
402
+ error?: string;
403
+ }>;
404
+ verifyEmail: (token: string) => Promise<{
405
+ success: boolean;
406
+ error?: string;
407
+ email?: string;
408
+ }>;
409
+ resendVerificationEmail: (email: string) => Promise<{
410
+ success: boolean;
411
+ error?: string;
412
+ }>;
413
+ }
414
+ /**
415
+ * Route an auth request to the appropriate handler.
416
+ * Returns null if the path doesn't match any auth route (caller handles 404).
417
+ *
418
+ * @param request - The incoming HTTP request
419
+ * @param authPath - The path after the auth prefix (e.g., "login", "setup-status")
420
+ * @param deps - Injected service dependencies
421
+ */
422
+ declare function routeAuthRequest(request: Request, authPath: string, deps: AuthRouterDeps): Promise<Response | null>;
423
+
424
+ export { authenticateApiKey, buildClaims, checkCollectionAccess, checkPermission, createErrorResponse, createJsonErrorResponse, generateRefreshToken, getSession, hasAllRoles, hasAnyRole, hasRole, hashPassword, hashRefreshToken, isErrorResponse, isSuperAdmin, requireAllRoles, requireAnyRole, requireAuth, requireRole, routeAuthRequest, secretToKey, signAccessToken, validatePasswordStrength, verifyAccessToken, verifyPassword };
425
+ export type { AuthContext, AuthRouterDeps, BuildClaimsInput, ErrorResponse, GetSessionResult, NextlyJwtPayload, PasswordStrengthResult, RefreshTokenRecord, SessionUser, VerifyResult };
@@ -0,0 +1,199 @@
1
+ import {
2
+ routeAuthRequest
3
+ } from "../chunk-A3WPLSDT.mjs";
4
+ import "../chunk-DXGGXIUZ.mjs";
5
+ import "../chunk-APKKRD2G.mjs";
6
+ import {
7
+ generateRefreshToken,
8
+ getSession,
9
+ hasAllRoles,
10
+ hasAnyRole,
11
+ hasRole,
12
+ hashRefreshToken
13
+ } from "../chunk-UUOFWCM6.mjs";
14
+ import {
15
+ verifyAccessToken
16
+ } from "../chunk-2ZFKXPQM.mjs";
17
+ import {
18
+ buildClaims,
19
+ secretToKey,
20
+ signAccessToken
21
+ } from "../chunk-X23WKS3Z.mjs";
22
+ import "../chunk-67GXH6PR.mjs";
23
+ import "../chunk-W4MGXIRR.mjs";
24
+ import {
25
+ hashPassword,
26
+ isSuperAdmin,
27
+ validatePasswordStrength,
28
+ verifyPassword
29
+ } from "../chunk-W5KKPZT5.mjs";
30
+ import "../chunk-56WO4WX7.mjs";
31
+ import "../chunk-2W3DVD7S.mjs";
32
+ import "../chunk-TS7GHTG2.mjs";
33
+ import "../chunk-H26B4FYG.mjs";
34
+ import "../chunk-DP3G27G5.mjs";
35
+ import "../chunk-DV6WVX2Q.mjs";
36
+ import "../chunk-UJ2IMJ4W.mjs";
37
+ import "../chunk-IUDOC7N7.mjs";
38
+ import "../chunk-LAZXX4HR.mjs";
39
+ import "../chunk-D5HQBNUB.mjs";
40
+ import {
41
+ NextlyError
42
+ } from "../chunk-NRUWQ5Z7.mjs";
43
+ import "../chunk-7P6ASYW6.mjs";
44
+
45
+ // src/auth/guards/require-auth.ts
46
+ async function requireAuth(request, secret) {
47
+ const result = await getSession(request, secret);
48
+ if (!result.authenticated) {
49
+ throw NextlyError.authRequired({
50
+ logContext: { reason: result.reason }
51
+ });
52
+ }
53
+ return result.user;
54
+ }
55
+
56
+ // src/auth/guards/require-role.ts
57
+ function requireRole(user, roleSlug) {
58
+ if (!hasRole(user, roleSlug)) {
59
+ throw NextlyError.forbidden({
60
+ logContext: {
61
+ action: "require-role",
62
+ requiredRole: roleSlug,
63
+ userId: user.id
64
+ }
65
+ });
66
+ }
67
+ }
68
+ function requireAnyRole(user, roleSlugs) {
69
+ if (!hasAnyRole(user, roleSlugs)) {
70
+ throw NextlyError.forbidden({
71
+ logContext: {
72
+ action: "require-any-role",
73
+ requiredRoles: roleSlugs,
74
+ userId: user.id
75
+ }
76
+ });
77
+ }
78
+ }
79
+ function requireAllRoles(user, roleSlugs) {
80
+ if (!hasAllRoles(user, roleSlugs)) {
81
+ throw NextlyError.forbidden({
82
+ logContext: {
83
+ action: "require-all-roles",
84
+ requiredRoles: roleSlugs,
85
+ userId: user.id
86
+ }
87
+ });
88
+ }
89
+ }
90
+
91
+ // src/auth/guards/require-permission.ts
92
+ function createErrorResponse(statusCode, message, error) {
93
+ return { success: false, statusCode, message, error, data: null };
94
+ }
95
+ function createJsonErrorResponse(errResp) {
96
+ const headers = {
97
+ "Content-Type": "application/json",
98
+ ...errResp.headers || {}
99
+ };
100
+ return new Response(JSON.stringify(errResp), {
101
+ status: errResp.statusCode,
102
+ headers
103
+ });
104
+ }
105
+ function isErrorResponse(value) {
106
+ return typeof value === "object" && value !== null && "success" in value && value.success === false && "statusCode" in value;
107
+ }
108
+ async function checkPermission(context, action, resource, deps) {
109
+ if (context.authMethod === "api-key") {
110
+ const permSlug = `${action}-${resource}`;
111
+ return context.permissions.get(permSlug) === true;
112
+ }
113
+ if (deps.checkUserPermission) {
114
+ return deps.checkUserPermission(context.userId, action, resource);
115
+ }
116
+ return false;
117
+ }
118
+
119
+ // src/auth/guards/require-api-key.ts
120
+ async function authenticateApiKey(request, deps) {
121
+ const authHeader = request.headers.get("authorization");
122
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
123
+ return null;
124
+ }
125
+ const rawKey = authHeader.slice(7);
126
+ if (!rawKey) {
127
+ return createErrorResponse(401, "Missing API key", "Unauthorized");
128
+ }
129
+ const result = await deps.validateApiKey(rawKey);
130
+ if (!result.valid) {
131
+ if (result.retryAfter) {
132
+ const resp = createErrorResponse(
133
+ 429,
134
+ "Rate limit exceeded",
135
+ "Too Many Requests"
136
+ );
137
+ resp.headers = { "Retry-After": String(result.retryAfter) };
138
+ return resp;
139
+ }
140
+ return createErrorResponse(
141
+ 401,
142
+ result.error || "Invalid API key",
143
+ "Unauthorized"
144
+ );
145
+ }
146
+ return {
147
+ userId: result.userId,
148
+ userName: "",
149
+ userEmail: "",
150
+ permissions: result.permissions || /* @__PURE__ */ new Map(),
151
+ roles: result.roles || [],
152
+ authMethod: "api-key"
153
+ };
154
+ }
155
+
156
+ // src/auth/guards/require-collection-access.ts
157
+ async function checkCollectionAccess(context, collectionSlug, operation, deps) {
158
+ if (context.roles.includes("super-admin")) {
159
+ return true;
160
+ }
161
+ if (deps.evaluateCodeAccess) {
162
+ const codeResult = await deps.evaluateCodeAccess(
163
+ collectionSlug,
164
+ operation,
165
+ context
166
+ );
167
+ if (codeResult !== null) {
168
+ return codeResult;
169
+ }
170
+ }
171
+ return checkPermission(context, operation, collectionSlug, deps);
172
+ }
173
+ export {
174
+ authenticateApiKey,
175
+ buildClaims,
176
+ checkCollectionAccess,
177
+ checkPermission,
178
+ createErrorResponse,
179
+ createJsonErrorResponse,
180
+ generateRefreshToken,
181
+ getSession,
182
+ hasAllRoles,
183
+ hasAnyRole,
184
+ hasRole,
185
+ hashPassword,
186
+ hashRefreshToken,
187
+ isErrorResponse,
188
+ isSuperAdmin,
189
+ requireAllRoles,
190
+ requireAnyRole,
191
+ requireAuth,
192
+ requireRole,
193
+ routeAuthRequest,
194
+ secretToKey,
195
+ signAccessToken,
196
+ validatePasswordStrength,
197
+ verifyAccessToken,
198
+ verifyPassword
199
+ };
@@ -0,0 +1,7 @@
1
+ import {
2
+ runBootTimeApplyIfDev
3
+ } from "./chunk-R6JJQHFC.mjs";
4
+ import "./chunk-7P6ASYW6.mjs";
5
+ export {
6
+ runBootTimeApplyIfDev
7
+ };