@de-otio/trellis 0.6.1 → 0.7.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 (339) hide show
  1. package/dist/env.d.ts +21 -0
  2. package/dist/env.d.ts.map +1 -1
  3. package/dist/env.js +12 -0
  4. package/dist/env.js.map +1 -1
  5. package/dist/lambda/nightly-cron.d.ts.map +1 -1
  6. package/dist/lambda/nightly-cron.js +5 -2
  7. package/dist/lambda/nightly-cron.js.map +1 -1
  8. package/dist/lambda/post-confirmation.d.ts +30 -0
  9. package/dist/lambda/post-confirmation.d.ts.map +1 -1
  10. package/dist/lambda/post-confirmation.js +333 -29
  11. package/dist/lambda/post-confirmation.js.map +1 -1
  12. package/dist/lambda/pre-token-generation.d.ts +20 -0
  13. package/dist/lambda/pre-token-generation.d.ts.map +1 -1
  14. package/dist/lambda/pre-token-generation.js +233 -48
  15. package/dist/lambda/pre-token-generation.js.map +1 -1
  16. package/dist/lib/activitypub/activity-processor.d.ts.map +1 -1
  17. package/dist/lib/activitypub/activity-processor.js +2 -1
  18. package/dist/lib/activitypub/activity-processor.js.map +1 -1
  19. package/dist/lib/activitypub/group-service.d.ts +2 -2
  20. package/dist/lib/activitypub/group-service.d.ts.map +1 -1
  21. package/dist/lib/activitypub/group-service.js +5 -2
  22. package/dist/lib/activitypub/group-service.js.map +1 -1
  23. package/dist/lib/age-tier-transition.d.ts.map +1 -1
  24. package/dist/lib/age-tier-transition.js +19 -10
  25. package/dist/lib/age-tier-transition.js.map +1 -1
  26. package/dist/lib/audit/csv-export.d.ts +25 -0
  27. package/dist/lib/audit/csv-export.d.ts.map +1 -0
  28. package/dist/lib/audit/csv-export.js +54 -0
  29. package/dist/lib/audit/csv-export.js.map +1 -0
  30. package/dist/lib/audit/emit.d.ts +56 -0
  31. package/dist/lib/audit/emit.d.ts.map +1 -0
  32. package/dist/lib/audit/emit.js +124 -0
  33. package/dist/lib/audit/emit.js.map +1 -0
  34. package/dist/lib/audit/event-types.d.ts +36 -0
  35. package/dist/lib/audit/event-types.d.ts.map +1 -0
  36. package/dist/lib/audit/event-types.js +69 -0
  37. package/dist/lib/audit/event-types.js.map +1 -0
  38. package/dist/lib/audit/pii-filter.d.ts +22 -0
  39. package/dist/lib/audit/pii-filter.d.ts.map +1 -0
  40. package/dist/lib/audit/pii-filter.js +51 -0
  41. package/dist/lib/audit/pii-filter.js.map +1 -0
  42. package/dist/lib/audit-logger.js +1 -1
  43. package/dist/lib/audit-logger.js.map +1 -1
  44. package/dist/lib/auth/auth-context.d.ts +34 -0
  45. package/dist/lib/auth/auth-context.d.ts.map +1 -0
  46. package/dist/lib/auth/auth-context.js +10 -0
  47. package/dist/lib/auth/auth-context.js.map +1 -0
  48. package/dist/lib/auth/auth-middleware.d.ts +50 -0
  49. package/dist/lib/auth/auth-middleware.d.ts.map +1 -0
  50. package/dist/lib/auth/auth-middleware.js +153 -0
  51. package/dist/lib/auth/auth-middleware.js.map +1 -0
  52. package/dist/lib/auth/capabilities.d.ts +40 -0
  53. package/dist/lib/auth/capabilities.d.ts.map +1 -0
  54. package/dist/lib/auth/capabilities.js +44 -0
  55. package/dist/lib/auth/capabilities.js.map +1 -0
  56. package/dist/lib/auth/claims-cache.d.ts +70 -0
  57. package/dist/lib/auth/claims-cache.d.ts.map +1 -0
  58. package/dist/lib/auth/claims-cache.js +139 -0
  59. package/dist/lib/auth/claims-cache.js.map +1 -0
  60. package/dist/lib/auth/cognito-jwt.d.ts +6 -0
  61. package/dist/lib/auth/cognito-jwt.d.ts.map +1 -1
  62. package/dist/lib/auth/cognito-jwt.js.map +1 -1
  63. package/dist/lib/auth/idp-redirect-builder.d.ts +43 -0
  64. package/dist/lib/auth/idp-redirect-builder.d.ts.map +1 -0
  65. package/dist/lib/auth/idp-redirect-builder.js +48 -0
  66. package/dist/lib/auth/idp-redirect-builder.js.map +1 -0
  67. package/dist/lib/auth/require.d.ts +51 -0
  68. package/dist/lib/auth/require.d.ts.map +1 -0
  69. package/dist/lib/auth/require.js +99 -0
  70. package/dist/lib/auth/require.js.map +1 -0
  71. package/dist/lib/auth/role-grants.d.ts +18 -0
  72. package/dist/lib/auth/role-grants.d.ts.map +1 -0
  73. package/dist/lib/auth/role-grants.js +62 -0
  74. package/dist/lib/auth/role-grants.js.map +1 -0
  75. package/dist/lib/cognito/idp-sdk.d.ts +80 -0
  76. package/dist/lib/cognito/idp-sdk.d.ts.map +1 -0
  77. package/dist/lib/cognito/idp-sdk.js +186 -0
  78. package/dist/lib/cognito/idp-sdk.js.map +1 -0
  79. package/dist/lib/cognito/issuer-probe.d.ts +47 -0
  80. package/dist/lib/cognito/issuer-probe.d.ts.map +1 -0
  81. package/dist/lib/cognito/issuer-probe.js +319 -0
  82. package/dist/lib/cognito/issuer-probe.js.map +1 -0
  83. package/dist/lib/comment-handler.d.ts +7 -7
  84. package/dist/lib/comment-handler.d.ts.map +1 -1
  85. package/dist/lib/comment-handler.js +23 -20
  86. package/dist/lib/comment-handler.js.map +1 -1
  87. package/dist/lib/compliance/baseline.d.ts +15 -0
  88. package/dist/lib/compliance/baseline.d.ts.map +1 -0
  89. package/dist/lib/compliance/baseline.js +205 -0
  90. package/dist/lib/compliance/baseline.js.map +1 -0
  91. package/dist/lib/compliance/tenant-merge.d.ts +35 -0
  92. package/dist/lib/compliance/tenant-merge.d.ts.map +1 -0
  93. package/dist/lib/compliance/tenant-merge.js +80 -0
  94. package/dist/lib/compliance/tenant-merge.js.map +1 -0
  95. package/dist/lib/compliance/types.d.ts +135 -0
  96. package/dist/lib/compliance/types.d.ts.map +1 -0
  97. package/dist/lib/compliance/types.js +9 -0
  98. package/dist/lib/compliance/types.js.map +1 -0
  99. package/dist/lib/connection-code-handler.d.ts +4 -4
  100. package/dist/lib/connection-code-handler.d.ts.map +1 -1
  101. package/dist/lib/connection-code-handler.js +21 -11
  102. package/dist/lib/connection-code-handler.js.map +1 -1
  103. package/dist/lib/feed-handler.d.ts +2 -2
  104. package/dist/lib/feed-handler.d.ts.map +1 -1
  105. package/dist/lib/feed-handler.js +5 -9
  106. package/dist/lib/feed-handler.js.map +1 -1
  107. package/dist/lib/middleware/idempotency-store.d.ts +86 -0
  108. package/dist/lib/middleware/idempotency-store.d.ts.map +1 -0
  109. package/dist/lib/middleware/idempotency-store.js +109 -0
  110. package/dist/lib/middleware/idempotency-store.js.map +1 -0
  111. package/dist/lib/middleware/idempotency.d.ts +37 -0
  112. package/dist/lib/middleware/idempotency.d.ts.map +1 -0
  113. package/dist/lib/middleware/idempotency.js +358 -0
  114. package/dist/lib/middleware/idempotency.js.map +1 -0
  115. package/dist/lib/net/trusted-client-ip.d.ts +39 -0
  116. package/dist/lib/net/trusted-client-ip.d.ts.map +1 -0
  117. package/dist/lib/net/trusted-client-ip.js +100 -0
  118. package/dist/lib/net/trusted-client-ip.js.map +1 -0
  119. package/dist/lib/notification-handler.d.ts +5 -5
  120. package/dist/lib/notification-handler.d.ts.map +1 -1
  121. package/dist/lib/notification-handler.js +11 -9
  122. package/dist/lib/notification-handler.js.map +1 -1
  123. package/dist/lib/oauth/cognito-issuer.d.ts +34 -0
  124. package/dist/lib/oauth/cognito-issuer.d.ts.map +1 -0
  125. package/dist/lib/oauth/cognito-issuer.js +53 -0
  126. package/dist/lib/oauth/cognito-issuer.js.map +1 -0
  127. package/dist/lib/oauth/device-authorization.d.ts +145 -0
  128. package/dist/lib/oauth/device-authorization.d.ts.map +1 -0
  129. package/dist/lib/oauth/device-authorization.js +312 -0
  130. package/dist/lib/oauth/device-authorization.js.map +1 -0
  131. package/dist/lib/oauth/envelope-crypto.d.ts +101 -0
  132. package/dist/lib/oauth/envelope-crypto.d.ts.map +1 -0
  133. package/dist/lib/oauth/envelope-crypto.js +223 -0
  134. package/dist/lib/oauth/envelope-crypto.js.map +1 -0
  135. package/dist/lib/oauth/refresh-detection.d.ts +126 -0
  136. package/dist/lib/oauth/refresh-detection.d.ts.map +1 -0
  137. package/dist/lib/oauth/refresh-detection.js +248 -0
  138. package/dist/lib/oauth/refresh-detection.js.map +1 -0
  139. package/dist/lib/openapi/generator.d.ts +78 -0
  140. package/dist/lib/openapi/generator.d.ts.map +1 -0
  141. package/dist/lib/openapi/generator.js +201 -0
  142. package/dist/lib/openapi/generator.js.map +1 -0
  143. package/dist/lib/post-handler.d.ts +1 -1
  144. package/dist/lib/post-handler.d.ts.map +1 -1
  145. package/dist/lib/post-handler.js +4 -15
  146. package/dist/lib/post-handler.js.map +1 -1
  147. package/dist/lib/rate-limit.d.ts.map +1 -1
  148. package/dist/lib/rate-limit.js +11 -3
  149. package/dist/lib/rate-limit.js.map +1 -1
  150. package/dist/lib/routes/agent-authorize.d.ts +32 -0
  151. package/dist/lib/routes/agent-authorize.d.ts.map +1 -0
  152. package/dist/lib/routes/agent-authorize.js +479 -0
  153. package/dist/lib/routes/agent-authorize.js.map +1 -0
  154. package/dist/lib/routes/agent-sessions.d.ts +20 -0
  155. package/dist/lib/routes/agent-sessions.d.ts.map +1 -0
  156. package/dist/lib/routes/agent-sessions.js +124 -0
  157. package/dist/lib/routes/agent-sessions.js.map +1 -0
  158. package/dist/lib/routes/agent-surface.d.ts +37 -0
  159. package/dist/lib/routes/agent-surface.d.ts.map +1 -0
  160. package/dist/lib/routes/agent-surface.js +208 -0
  161. package/dist/lib/routes/agent-surface.js.map +1 -0
  162. package/dist/lib/routes/auth-discover.d.ts +18 -0
  163. package/dist/lib/routes/auth-discover.d.ts.map +1 -0
  164. package/dist/lib/routes/auth-discover.js +177 -0
  165. package/dist/lib/routes/auth-discover.js.map +1 -0
  166. package/dist/lib/routes/comments.d.ts.map +1 -1
  167. package/dist/lib/routes/comments.js +36 -7
  168. package/dist/lib/routes/comments.js.map +1 -1
  169. package/dist/lib/routes/connection-codes.d.ts.map +1 -1
  170. package/dist/lib/routes/connection-codes.js +21 -4
  171. package/dist/lib/routes/connection-codes.js.map +1 -1
  172. package/dist/lib/routes/content-discovery.d.ts.map +1 -1
  173. package/dist/lib/routes/content-discovery.js +18 -13
  174. package/dist/lib/routes/content-discovery.js.map +1 -1
  175. package/dist/lib/routes/dashboard.js +1 -1
  176. package/dist/lib/routes/dashboard.js.map +1 -1
  177. package/dist/lib/routes/employees.d.ts.map +1 -1
  178. package/dist/lib/routes/employees.js +57 -15
  179. package/dist/lib/routes/employees.js.map +1 -1
  180. package/dist/lib/routes/entities.d.ts.map +1 -1
  181. package/dist/lib/routes/entities.js +35 -19
  182. package/dist/lib/routes/entities.js.map +1 -1
  183. package/dist/lib/routes/errors.d.ts +34 -0
  184. package/dist/lib/routes/errors.d.ts.map +1 -0
  185. package/dist/lib/routes/errors.js +57 -0
  186. package/dist/lib/routes/errors.js.map +1 -0
  187. package/dist/lib/routes/feeds.d.ts.map +1 -1
  188. package/dist/lib/routes/feeds.js +12 -2
  189. package/dist/lib/routes/feeds.js.map +1 -1
  190. package/dist/lib/routes/index.d.ts.map +1 -1
  191. package/dist/lib/routes/index.js +50 -0
  192. package/dist/lib/routes/index.js.map +1 -1
  193. package/dist/lib/routes/mfa.d.ts.map +1 -1
  194. package/dist/lib/routes/mfa.js +1 -0
  195. package/dist/lib/routes/mfa.js.map +1 -1
  196. package/dist/lib/routes/notifications.d.ts.map +1 -1
  197. package/dist/lib/routes/notifications.js +21 -4
  198. package/dist/lib/routes/notifications.js.map +1 -1
  199. package/dist/lib/routes/oauth.d.ts +15 -0
  200. package/dist/lib/routes/oauth.d.ts.map +1 -0
  201. package/dist/lib/routes/oauth.js +139 -0
  202. package/dist/lib/routes/oauth.js.map +1 -0
  203. package/dist/lib/routes/posts.d.ts.map +1 -1
  204. package/dist/lib/routes/posts.js +30 -19
  205. package/dist/lib/routes/posts.js.map +1 -1
  206. package/dist/lib/routes/products.d.ts.map +1 -1
  207. package/dist/lib/routes/products.js +19 -22
  208. package/dist/lib/routes/products.js.map +1 -1
  209. package/dist/lib/routes/setup-status.d.ts +34 -0
  210. package/dist/lib/routes/setup-status.d.ts.map +1 -0
  211. package/dist/lib/routes/setup-status.js +87 -0
  212. package/dist/lib/routes/setup-status.js.map +1 -0
  213. package/dist/lib/routes/taxonomy-analytics.d.ts.map +1 -1
  214. package/dist/lib/routes/taxonomy-analytics.js +15 -14
  215. package/dist/lib/routes/taxonomy-analytics.js.map +1 -1
  216. package/dist/lib/routes/taxonomy.d.ts.map +1 -1
  217. package/dist/lib/routes/taxonomy.js +19 -16
  218. package/dist/lib/routes/taxonomy.js.map +1 -1
  219. package/dist/lib/routes/tenant-audit.d.ts +19 -0
  220. package/dist/lib/routes/tenant-audit.d.ts.map +1 -0
  221. package/dist/lib/routes/tenant-audit.js +244 -0
  222. package/dist/lib/routes/tenant-audit.js.map +1 -0
  223. package/dist/lib/routes/tenant-compliance.d.ts +21 -0
  224. package/dist/lib/routes/tenant-compliance.d.ts.map +1 -0
  225. package/dist/lib/routes/tenant-compliance.js +122 -0
  226. package/dist/lib/routes/tenant-compliance.js.map +1 -0
  227. package/dist/lib/routes/tenant-domains.d.ts +11 -0
  228. package/dist/lib/routes/tenant-domains.d.ts.map +1 -0
  229. package/dist/lib/routes/tenant-domains.js +95 -0
  230. package/dist/lib/routes/tenant-domains.js.map +1 -0
  231. package/dist/lib/routes/tenant-idp.d.ts +3 -0
  232. package/dist/lib/routes/tenant-idp.d.ts.map +1 -0
  233. package/dist/lib/routes/tenant-idp.js +89 -0
  234. package/dist/lib/routes/tenant-idp.js.map +1 -0
  235. package/dist/lib/routes/tenant-members.d.ts +13 -0
  236. package/dist/lib/routes/tenant-members.d.ts.map +1 -0
  237. package/dist/lib/routes/tenant-members.js +75 -0
  238. package/dist/lib/routes/tenant-members.js.map +1 -0
  239. package/dist/lib/routes/tenant-role-mappings.d.ts +11 -0
  240. package/dist/lib/routes/tenant-role-mappings.d.ts.map +1 -0
  241. package/dist/lib/routes/tenant-role-mappings.js +90 -0
  242. package/dist/lib/routes/tenant-role-mappings.js.map +1 -0
  243. package/dist/lib/routes/tenants.d.ts +13 -0
  244. package/dist/lib/routes/tenants.d.ts.map +1 -0
  245. package/dist/lib/routes/tenants.js +121 -0
  246. package/dist/lib/routes/tenants.js.map +1 -0
  247. package/dist/lib/routes/types.d.ts +9 -0
  248. package/dist/lib/routes/types.d.ts.map +1 -1
  249. package/dist/lib/schemas.d.ts +2 -2
  250. package/dist/lib/secrets/idp-secrets.d.ts +51 -0
  251. package/dist/lib/secrets/idp-secrets.d.ts.map +1 -0
  252. package/dist/lib/secrets/idp-secrets.js +111 -0
  253. package/dist/lib/secrets/idp-secrets.js.map +1 -0
  254. package/dist/lib/security-monitor.d.ts.map +1 -1
  255. package/dist/lib/security-monitor.js +6 -1
  256. package/dist/lib/security-monitor.js.map +1 -1
  257. package/dist/lib/session-manager.d.ts +1 -0
  258. package/dist/lib/session-manager.d.ts.map +1 -1
  259. package/dist/lib/session-manager.js.map +1 -1
  260. package/dist/lib/taxonomy-handler-factory.d.ts +4 -2
  261. package/dist/lib/taxonomy-handler-factory.d.ts.map +1 -1
  262. package/dist/lib/taxonomy-handler-factory.js +8 -7
  263. package/dist/lib/taxonomy-handler-factory.js.map +1 -1
  264. package/dist/lib/tenant/audit-emit.d.ts +18 -0
  265. package/dist/lib/tenant/audit-emit.d.ts.map +1 -0
  266. package/dist/lib/tenant/audit-emit.js +16 -0
  267. package/dist/lib/tenant/audit-emit.js.map +1 -0
  268. package/dist/lib/tenant/derive-domain.d.ts +19 -0
  269. package/dist/lib/tenant/derive-domain.d.ts.map +1 -0
  270. package/dist/lib/tenant/derive-domain.js +38 -0
  271. package/dist/lib/tenant/derive-domain.js.map +1 -0
  272. package/dist/lib/tenant/domain-handler.d.ts +42 -0
  273. package/dist/lib/tenant/domain-handler.d.ts.map +1 -0
  274. package/dist/lib/tenant/domain-handler.js +344 -0
  275. package/dist/lib/tenant/domain-handler.js.map +1 -0
  276. package/dist/lib/tenant/domain-validator.d.ts +28 -0
  277. package/dist/lib/tenant/domain-validator.d.ts.map +1 -0
  278. package/dist/lib/tenant/domain-validator.js +145 -0
  279. package/dist/lib/tenant/domain-validator.js.map +1 -0
  280. package/dist/lib/tenant/domain-verifier.d.ts +30 -0
  281. package/dist/lib/tenant/domain-verifier.d.ts.map +1 -0
  282. package/dist/lib/tenant/domain-verifier.js +53 -0
  283. package/dist/lib/tenant/domain-verifier.js.map +1 -0
  284. package/dist/lib/tenant/idp-handler.d.ts +29 -0
  285. package/dist/lib/tenant/idp-handler.d.ts.map +1 -0
  286. package/dist/lib/tenant/idp-handler.js +693 -0
  287. package/dist/lib/tenant/idp-handler.js.map +1 -0
  288. package/dist/lib/tenant/idp-name.d.ts +2 -0
  289. package/dist/lib/tenant/idp-name.d.ts.map +1 -0
  290. package/dist/lib/tenant/idp-name.js +20 -0
  291. package/dist/lib/tenant/idp-name.js.map +1 -0
  292. package/dist/lib/tenant/member-handler.d.ts +31 -0
  293. package/dist/lib/tenant/member-handler.d.ts.map +1 -0
  294. package/dist/lib/tenant/member-handler.js +343 -0
  295. package/dist/lib/tenant/member-handler.js.map +1 -0
  296. package/dist/lib/tenant/reserved-slugs.d.ts +37 -0
  297. package/dist/lib/tenant/reserved-slugs.d.ts.map +1 -0
  298. package/dist/lib/tenant/reserved-slugs.js +116 -0
  299. package/dist/lib/tenant/reserved-slugs.js.map +1 -0
  300. package/dist/lib/tenant/resolve-role.d.ts +39 -0
  301. package/dist/lib/tenant/resolve-role.d.ts.map +1 -0
  302. package/dist/lib/tenant/resolve-role.js +60 -0
  303. package/dist/lib/tenant/resolve-role.js.map +1 -0
  304. package/dist/lib/tenant/role-mapping-handler.d.ts +26 -0
  305. package/dist/lib/tenant/role-mapping-handler.d.ts.map +1 -0
  306. package/dist/lib/tenant/role-mapping-handler.js +260 -0
  307. package/dist/lib/tenant/role-mapping-handler.js.map +1 -0
  308. package/dist/lib/tenant/setup-status.d.ts +83 -0
  309. package/dist/lib/tenant/setup-status.d.ts.map +1 -0
  310. package/dist/lib/tenant/setup-status.js +201 -0
  311. package/dist/lib/tenant/setup-status.js.map +1 -0
  312. package/dist/lib/tenant/slug-validator.d.ts +31 -0
  313. package/dist/lib/tenant/slug-validator.d.ts.map +1 -0
  314. package/dist/lib/tenant/slug-validator.js +42 -0
  315. package/dist/lib/tenant/slug-validator.js.map +1 -0
  316. package/dist/lib/tenant/tenant-handler.d.ts +49 -0
  317. package/dist/lib/tenant/tenant-handler.d.ts.map +1 -0
  318. package/dist/lib/tenant/tenant-handler.js +377 -0
  319. package/dist/lib/tenant/tenant-handler.js.map +1 -0
  320. package/dist/lib/tenant/transfer-ownership.d.ts +39 -0
  321. package/dist/lib/tenant/transfer-ownership.d.ts.map +1 -0
  322. package/dist/lib/tenant/transfer-ownership.js +66 -0
  323. package/dist/lib/tenant/transfer-ownership.js.map +1 -0
  324. package/dist/lib/user/derive-handle.d.ts +29 -0
  325. package/dist/lib/user/derive-handle.d.ts.map +1 -0
  326. package/dist/lib/user/derive-handle.js +65 -0
  327. package/dist/lib/user/derive-handle.js.map +1 -0
  328. package/dist/lib/user-deprovisioning.d.ts +11 -1
  329. package/dist/lib/user-deprovisioning.d.ts.map +1 -1
  330. package/dist/lib/user-deprovisioning.js +46 -2
  331. package/dist/lib/user-deprovisioning.js.map +1 -1
  332. package/dist/lib/validation/feature-toggle-schemas.d.ts +10 -10
  333. package/package.json +5 -3
  334. package/prisma/migrations/20260502094501_add_tenancy_model/migration.sql +334 -0
  335. package/prisma/migrations/20260503000000_add_tenant_region/migration.sql +4 -0
  336. package/prisma/schema.prisma +324 -74
  337. package/src/lambda/nightly-cron.ts +4 -1
  338. package/src/lambda/post-confirmation.ts +405 -29
  339. package/src/lambda/pre-token-generation.ts +300 -59
@@ -0,0 +1,344 @@
1
+ "use strict";
2
+ /**
3
+ * Tenant Domain Handler
4
+ *
5
+ * Handles CRUD + DNS verification for tenant-claimed domains.
6
+ *
7
+ * Routes:
8
+ * POST /api/tenants/:id/domains — claim domain
9
+ * GET /api/tenants/:id/domains — list domains
10
+ * DELETE /api/tenants/:id/domains/:domainId — remove domain
11
+ * POST /api/tenants/:id/domains/:domainId/verify — trigger DNS check
12
+ */
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.DomainHandler = void 0;
48
+ const node_crypto_1 = require("node:crypto");
49
+ const auth_middleware_1 = require("../auth/auth-middleware");
50
+ const require_1 = require("../auth/require");
51
+ const domain_validator_1 = require("./domain-validator");
52
+ const domain_verifier_1 = require("./domain-verifier");
53
+ // ─── Constants ───────────────────────────────────────────────────────────────
54
+ const TOKEN_EXPIRY_DAYS = 7;
55
+ const RATE_LIMIT_WINDOW_SECONDS = 3600; // 1 hour
56
+ const RATE_LIMIT_MAX_ATTEMPTS = 10;
57
+ const FAILURE_ROTATION_WINDOW_SECONDS = 86400; // 24 hours
58
+ const FAILURE_ROTATION_THRESHOLD = 10;
59
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
60
+ function generateToken() {
61
+ return (0, node_crypto_1.randomBytes)(16).toString("hex");
62
+ }
63
+ function tokenExpiresAt() {
64
+ const d = new Date();
65
+ d.setDate(d.getDate() + TOKEN_EXPIRY_DAYS);
66
+ return d;
67
+ }
68
+ function makeRateLimitKey(tenantId, domainId) {
69
+ return `domain-rate:${tenantId}:${domainId}`;
70
+ }
71
+ function makeFailureCountKey(tenantId, domainId) {
72
+ return `domain-fail:${tenantId}:${domainId}`;
73
+ }
74
+ function json(body, status) {
75
+ return new Response(JSON.stringify(body), {
76
+ status,
77
+ headers: { "content-type": "application/json" },
78
+ });
79
+ }
80
+ // ─── Handler ─────────────────────────────────────────────────────────────────
81
+ class DomainHandler {
82
+ /**
83
+ * POST /api/tenants/:id/domains
84
+ * Claim a domain. Returns 201 + token on first claim, 200 + existing record
85
+ * if the same tenant re-claims the same domain (idempotent).
86
+ * Returns 409 if the domain is already claimed by another tenant.
87
+ */
88
+ async handleClaim(tenantId, request, auth, env) {
89
+ const denied = (0, auth_middleware_1.requireActiveTenant)(auth, tenantId) ??
90
+ (0, require_1.requireRole)(auth, "ADMIN");
91
+ if (denied)
92
+ return denied;
93
+ const { z } = await Promise.resolve().then(() => __importStar(require("zod")));
94
+ let body;
95
+ try {
96
+ body = await request.json();
97
+ }
98
+ catch {
99
+ return json({ error: "INVALID_JSON", message: "Request body must be valid JSON" }, 400);
100
+ }
101
+ const schema = z.object({ domain: z.string() });
102
+ const parsed = schema.safeParse(body);
103
+ if (!parsed.success) {
104
+ return json({ error: "VALIDATION_ERROR", message: "domain is required" }, 400);
105
+ }
106
+ const validation = (0, domain_validator_1.validateDomain)(parsed.data.domain);
107
+ if (!validation.ok) {
108
+ return json({ error: validation.code, message: validation.message }, 400);
109
+ }
110
+ const { domain } = validation;
111
+ const { createPrisma } = await Promise.resolve().then(() => __importStar(require("../../db")));
112
+ const db = createPrisma(env);
113
+ // Check for existing record globally (without tenantId filter) to enforce
114
+ // the "domain can only belong to one tenant" rule.
115
+ const existing = await db.tenantDomain.findUnique({
116
+ where: { domain },
117
+ });
118
+ if (existing) {
119
+ if (existing.tenantId === tenantId) {
120
+ // Idempotent re-claim: check if token has expired and re-issue if so.
121
+ if (!existing.verifiedAt && existing.tokenExpiresAt < new Date()) {
122
+ // Expired unverified token — rotate it.
123
+ const updated = await db.tenantDomain.update({
124
+ where: { id: existing.id },
125
+ data: {
126
+ verificationToken: generateToken(),
127
+ tokenExpiresAt: tokenExpiresAt(),
128
+ verifyAttempts: 0,
129
+ },
130
+ });
131
+ return json(formatDomainRecord(updated), 200);
132
+ }
133
+ // Return existing record as-is.
134
+ return json(formatDomainRecord(existing), 200);
135
+ }
136
+ // Another tenant owns it — don't leak which tenant.
137
+ return json({ error: "DOMAIN_CONFLICT", message: "Domain is already claimed" }, 409);
138
+ }
139
+ // Create new record.
140
+ const record = await db.tenantDomain.create({
141
+ data: {
142
+ tenantId,
143
+ domain,
144
+ verificationToken: generateToken(),
145
+ tokenExpiresAt: tokenExpiresAt(),
146
+ },
147
+ });
148
+ return json(formatDomainRecord(record), 201);
149
+ }
150
+ /**
151
+ * GET /api/tenants/:id/domains
152
+ * Lists tenant's domains. Tokens are included for unverified records
153
+ * (so the admin can copy the required TXT value).
154
+ */
155
+ async handleList(tenantId, auth, env) {
156
+ const denied = (0, auth_middleware_1.requireActiveTenant)(auth, tenantId);
157
+ if (denied)
158
+ return denied;
159
+ const { createPrisma } = await Promise.resolve().then(() => __importStar(require("../../db")));
160
+ const db = createPrisma(env);
161
+ const domains = await db.tenantDomain.findMany({
162
+ where: { tenantId },
163
+ orderBy: { createdAt: "asc" },
164
+ });
165
+ return json({ domains: domains.map(formatDomainRecord) }, 200);
166
+ }
167
+ /**
168
+ * DELETE /api/tenants/:id/domains/:domainId
169
+ * Removes a domain. Blocked if the domain is the only verified domain and
170
+ * the tenant has an ACTIVE IdP (removing it would break SSO logins).
171
+ */
172
+ async handleDelete(tenantId, domainId, auth, env) {
173
+ const denied = (0, auth_middleware_1.requireActiveTenant)(auth, tenantId) ??
174
+ (0, require_1.requireRole)(auth, "ADMIN");
175
+ if (denied)
176
+ return denied;
177
+ const { createPrisma } = await Promise.resolve().then(() => __importStar(require("../../db")));
178
+ const db = createPrisma(env);
179
+ const record = await db.tenantDomain.findUnique({
180
+ where: { id: domainId, tenantId },
181
+ });
182
+ if (!record) {
183
+ return json({ error: "NOT_FOUND", message: "Domain not found" }, 404);
184
+ }
185
+ if (record.verifiedAt) {
186
+ // Check whether this is the last verified domain and there's an active IdP.
187
+ const verifiedCount = await db.tenantDomain.count({
188
+ where: { tenantId, verifiedAt: { not: null } },
189
+ });
190
+ if (verifiedCount === 1) {
191
+ const idp = await db.tenantIdentityProvider.findUnique({
192
+ where: { tenantId },
193
+ select: { status: true },
194
+ });
195
+ if (idp?.status === "ACTIVE") {
196
+ return json({
197
+ error: "DOMAIN_IN_USE",
198
+ message: "Cannot remove the only verified domain while an IdP is active",
199
+ remediation: "Disable the identity provider first, or add another verified domain",
200
+ }, 409);
201
+ }
202
+ }
203
+ }
204
+ await db.tenantDomain.delete({ where: { id: domainId } });
205
+ return json({ ok: true }, 200);
206
+ }
207
+ /**
208
+ * POST /api/tenants/:id/domains/:domainId/verify
209
+ * Performs a DNS TXT lookup to verify domain ownership.
210
+ */
211
+ async handleVerify(tenantId, domainId, auth, env) {
212
+ const denied = (0, auth_middleware_1.requireActiveTenant)(auth, tenantId) ??
213
+ (0, require_1.requireRole)(auth, "ADMIN");
214
+ if (denied)
215
+ return denied;
216
+ const { createPrisma } = await Promise.resolve().then(() => __importStar(require("../../db")));
217
+ const db = createPrisma(env);
218
+ const record = await db.tenantDomain.findUnique({
219
+ where: { id: domainId, tenantId },
220
+ });
221
+ if (!record) {
222
+ return json({ error: "NOT_FOUND", message: "Domain not found" }, 404);
223
+ }
224
+ // Already verified — return success immediately.
225
+ if (record.verifiedAt) {
226
+ return json({ ok: true, domain: record.domain, verifiedAt: record.verifiedAt }, 200);
227
+ }
228
+ // Check token expiry (sec finding #5).
229
+ if (record.tokenExpiresAt < new Date()) {
230
+ return json({
231
+ error: "TOKEN_EXPIRED",
232
+ message: "Verification token has expired",
233
+ remediation: "Re-claim the domain to get a new token",
234
+ }, 422);
235
+ }
236
+ // Rate limit: 10 verify attempts per hour per (tenantId, domainId).
237
+ const rateLimitKey = makeRateLimitKey(tenantId, domainId);
238
+ const rateLimitCheckResult = await this.checkAndIncrementRateLimit(env, rateLimitKey, RATE_LIMIT_MAX_ATTEMPTS, RATE_LIMIT_WINDOW_SECONDS);
239
+ if (rateLimitCheckResult.limited) {
240
+ return new Response(JSON.stringify({ error: "RATE_LIMITED", message: "Too many verify attempts" }), {
241
+ status: 429,
242
+ headers: {
243
+ "content-type": "application/json",
244
+ "Retry-After": String(rateLimitCheckResult.retryAfter),
245
+ },
246
+ });
247
+ }
248
+ // Perform DNS lookup.
249
+ const dnsResult = await (0, domain_verifier_1.verifyDomainToken)(record.domain, record.verificationToken);
250
+ if (dnsResult.verified) {
251
+ const updated = await db.tenantDomain.update({
252
+ where: { id: domainId },
253
+ data: {
254
+ verifiedAt: new Date(),
255
+ verifyAttemptedAt: new Date(),
256
+ verifyAttempts: { increment: 1 },
257
+ },
258
+ });
259
+ return json({ ok: true, domain: updated.domain, verifiedAt: updated.verifiedAt }, 200);
260
+ }
261
+ // Failed — increment failure counter in DynamoDB.
262
+ await db.tenantDomain.update({
263
+ where: { id: domainId },
264
+ data: {
265
+ verifyAttemptedAt: new Date(),
266
+ verifyAttempts: { increment: 1 },
267
+ },
268
+ });
269
+ // Check for auto-rotation after 10 failures in 24h.
270
+ const failKey = makeFailureCountKey(tenantId, domainId);
271
+ const newFailCount = await this.incrementFailureCount(env, failKey, FAILURE_ROTATION_WINDOW_SECONDS);
272
+ let rotated = false;
273
+ let newToken;
274
+ if (newFailCount >= FAILURE_ROTATION_THRESHOLD) {
275
+ newToken = generateToken();
276
+ await db.tenantDomain.update({
277
+ where: { id: domainId },
278
+ data: {
279
+ verificationToken: newToken,
280
+ tokenExpiresAt: tokenExpiresAt(),
281
+ verifyAttempts: 0,
282
+ },
283
+ });
284
+ await env.RATE_LIMIT_KV.delete(failKey);
285
+ rotated = true;
286
+ }
287
+ const reason = dnsResult.reason;
288
+ const messages = {
289
+ TOKEN_MISMATCH: "TXT record found but token did not match",
290
+ NO_RECORDS: "No TXT record found at the verification hostname",
291
+ DNS_ERROR: "DNS lookup failed — please try again later",
292
+ };
293
+ const body = {
294
+ error: "VERIFICATION_FAILED",
295
+ message: messages[reason] ?? "DNS verification failed",
296
+ remediation: `Add TXT record: _skybber-verify.${record.domain} = skybber-verify=${record.verificationToken}`,
297
+ };
298
+ if (rotated && newToken) {
299
+ body.tokenRotated = true;
300
+ body.remediation = `Token rotated after repeated failures. Add TXT record: _skybber-verify.${record.domain} = skybber-verify=${newToken}`;
301
+ }
302
+ return json(body, 422);
303
+ }
304
+ // ─── Internal helpers ───────────────────────────────────────────────────────
305
+ async checkAndIncrementRateLimit(env, key, maxAttempts, windowSeconds) {
306
+ const raw = await env.RATE_LIMIT_KV.get(key);
307
+ const current = raw ? parseInt(raw, 10) : 0;
308
+ if (current >= maxAttempts) {
309
+ return { limited: true, retryAfter: windowSeconds };
310
+ }
311
+ await env.RATE_LIMIT_KV.put(key, String(current + 1), {
312
+ expirationTtl: windowSeconds,
313
+ });
314
+ return { limited: false };
315
+ }
316
+ async incrementFailureCount(env, key, windowSeconds) {
317
+ const raw = await env.RATE_LIMIT_KV.get(key);
318
+ const current = raw ? parseInt(raw, 10) : 0;
319
+ const next = current + 1;
320
+ await env.RATE_LIMIT_KV.put(key, String(next), {
321
+ expirationTtl: windowSeconds,
322
+ });
323
+ return next;
324
+ }
325
+ }
326
+ exports.DomainHandler = DomainHandler;
327
+ function formatDomainRecord(r) {
328
+ return {
329
+ id: r.id,
330
+ tenantId: r.tenantId,
331
+ domain: r.domain,
332
+ verifiedAt: r.verifiedAt,
333
+ tokenExpiresAt: r.tokenExpiresAt,
334
+ verifyAttempts: r.verifyAttempts,
335
+ createdAt: r.createdAt,
336
+ // Only include token for unverified records so admin knows the TXT value to set.
337
+ ...(r.verifiedAt ? {} : {
338
+ verificationToken: r.verificationToken,
339
+ txtRecord: `skybber-verify=${r.verificationToken}`,
340
+ txtHost: `_skybber-verify.${r.domain}`,
341
+ }),
342
+ };
343
+ }
344
+ //# sourceMappingURL=domain-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-handler.js","sourceRoot":"","sources":["../../../src/lib/tenant/domain-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Domain Validator
3
+ *
4
+ * Validates domain strings for tenant domain claims:
5
+ * - Format: valid hostname, no protocol, no path, lowercase
6
+ * - PSL check: rejects registrable-domain roots and public suffixes themselves
7
+ * - Skybber namespace: rejects *.skybber.com sub-claims
8
+ *
9
+ * Uses a bundled PSL snapshot to avoid runtime network fetches.
10
+ */
11
+ export interface DomainValidationResult {
12
+ ok: true;
13
+ domain: string;
14
+ }
15
+ export interface DomainValidationError {
16
+ ok: false;
17
+ code: string;
18
+ message: string;
19
+ }
20
+ export type DomainValidationOutcome = DomainValidationResult | DomainValidationError;
21
+ /**
22
+ * Validates and normalises a domain string for tenant claiming.
23
+ *
24
+ * Returns `{ ok: true, domain }` with the normalised domain on success,
25
+ * or `{ ok: false, code, message }` with a rejection reason on failure.
26
+ */
27
+ export declare function validateDomain(raw: string): DomainValidationOutcome;
28
+ //# sourceMappingURL=domain-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-validator.d.ts","sourceRoot":"","sources":["../../../src/lib/tenant/domain-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAkGH,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,GAAG,qBAAqB,CAAC;AAIrF;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,uBAAuB,CAoDnE"}
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ /**
3
+ * Domain Validator
4
+ *
5
+ * Validates domain strings for tenant domain claims:
6
+ * - Format: valid hostname, no protocol, no path, lowercase
7
+ * - PSL check: rejects registrable-domain roots and public suffixes themselves
8
+ * - Skybber namespace: rejects *.skybber.com sub-claims
9
+ *
10
+ * Uses a bundled PSL snapshot to avoid runtime network fetches.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.validateDomain = validateDomain;
14
+ const node_fs_1 = require("node:fs");
15
+ const node_path_1 = require("node:path");
16
+ let pslCache = null;
17
+ function loadPsl() {
18
+ if (pslCache)
19
+ return pslCache;
20
+ const snapshotPath = (0, node_path_1.join)(__dirname, "psl-snapshot.txt");
21
+ const lines = (0, node_fs_1.readFileSync)(snapshotPath, "utf-8").split("\n");
22
+ const exact = new Set();
23
+ const wildcards = new Set();
24
+ const exceptions = new Set();
25
+ for (const raw of lines) {
26
+ const line = raw.trim();
27
+ if (!line || line.startsWith("//"))
28
+ continue;
29
+ if (line.startsWith("!")) {
30
+ exceptions.add(line.slice(1).toLowerCase());
31
+ }
32
+ else if (line.startsWith("*.")) {
33
+ wildcards.add(line.slice(2).toLowerCase());
34
+ }
35
+ else {
36
+ exact.add(line.toLowerCase());
37
+ }
38
+ }
39
+ pslCache = { exact, wildcards, exceptions };
40
+ return pslCache;
41
+ }
42
+ // ─── Public suffix check ─────────────────────────────────────────────────────
43
+ /**
44
+ * Returns true when `domain` is itself a public suffix or would be "owned"
45
+ * by a wildcard rule (i.e. not enough labels to form a registrable domain).
46
+ *
47
+ * Examples:
48
+ * isPublicSuffix("com") → true
49
+ * isPublicSuffix("co.uk") → true
50
+ * isPublicSuffix("example.com") → false (has a registrable label)
51
+ * isPublicSuffix("foo.*.ck") → false (exception *.ck: !www.ck applies)
52
+ */
53
+ function isPublicSuffix(domain) {
54
+ const psl = loadPsl();
55
+ const labels = domain.split(".");
56
+ if (psl.exact.has(domain))
57
+ return true;
58
+ // Check wildcard: if parent suffix matches a wildcard rule, this domain is
59
+ // itself just the minimum-registrable level — only subdomains of it are
60
+ // "registrable" beyond the PSL boundary.
61
+ if (labels.length >= 2) {
62
+ const parent = labels.slice(1).join(".");
63
+ if (psl.wildcards.has(parent)) {
64
+ if (!psl.exceptions.has(domain))
65
+ return true;
66
+ }
67
+ }
68
+ return false;
69
+ }
70
+ // ─── Basic hostname format check ─────────────────────────────────────────────
71
+ const LABEL_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
72
+ const MAX_DOMAIN_LENGTH = 253;
73
+ function isValidHostname(domain) {
74
+ if (!domain || domain.length > MAX_DOMAIN_LENGTH)
75
+ return false;
76
+ if (domain.startsWith("-") || domain.endsWith("-"))
77
+ return false;
78
+ if (domain.startsWith(".") || domain.endsWith("."))
79
+ return false;
80
+ const labels = domain.split(".");
81
+ if (labels.length < 2)
82
+ return false;
83
+ for (const label of labels) {
84
+ if (!LABEL_RE.test(label))
85
+ return false;
86
+ }
87
+ const tld = labels[labels.length - 1];
88
+ if (!tld || /^\d+$/.test(tld))
89
+ return false;
90
+ return true;
91
+ }
92
+ const SKYBBER_SUFFIX = ".skybber.com";
93
+ /**
94
+ * Validates and normalises a domain string for tenant claiming.
95
+ *
96
+ * Returns `{ ok: true, domain }` with the normalised domain on success,
97
+ * or `{ ok: false, code, message }` with a rejection reason on failure.
98
+ */
99
+ function validateDomain(raw) {
100
+ if (typeof raw !== "string" || !raw) {
101
+ return { ok: false, code: "INVALID_DOMAIN", message: "Domain must be a non-empty string" };
102
+ }
103
+ // Strip leading/trailing whitespace and convert to lowercase.
104
+ // Handle IDN: Node's URL parser supports punycode implicitly via WHATWG URL.
105
+ let domain = raw.trim().toLowerCase();
106
+ // Reject if it looks like a URL.
107
+ if (domain.includes("://") || domain.includes("/") || domain.includes("?")) {
108
+ return { ok: false, code: "INVALID_DOMAIN", message: "Domain must not include a protocol or path" };
109
+ }
110
+ // Strip port if present (e.g. "example.com:8080").
111
+ const colonIdx = domain.indexOf(":");
112
+ if (colonIdx !== -1) {
113
+ domain = domain.slice(0, colonIdx);
114
+ }
115
+ // Attempt IDN normalisation via WHATWG URL.
116
+ try {
117
+ const url = new URL(`http://${domain}`);
118
+ domain = url.hostname;
119
+ }
120
+ catch {
121
+ return { ok: false, code: "INVALID_DOMAIN", message: "Domain is not a valid hostname" };
122
+ }
123
+ // Check for public suffix before hostname validation so single-label TLDs
124
+ // ("com", "net") return PUBLIC_SUFFIX rather than INVALID_DOMAIN.
125
+ if (isPublicSuffix(domain)) {
126
+ return {
127
+ ok: false,
128
+ code: "PUBLIC_SUFFIX",
129
+ message: "Cannot claim a public suffix domain (e.g. com, co.uk, gmail.com is fine but co.uk is not)",
130
+ };
131
+ }
132
+ if (!isValidHostname(domain)) {
133
+ return { ok: false, code: "INVALID_DOMAIN", message: "Domain is not a valid hostname" };
134
+ }
135
+ // Reject *.skybber.com sub-claims.
136
+ if (domain.endsWith(SKYBBER_SUFFIX) || domain === "skybber.com") {
137
+ return {
138
+ ok: false,
139
+ code: "RESERVED_DOMAIN",
140
+ message: "Cannot claim a skybber.com subdomain",
141
+ };
142
+ }
143
+ return { ok: true, domain };
144
+ }
145
+ //# sourceMappingURL=domain-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-validator.js","sourceRoot":"","sources":["../../../src/lib/tenant/domain-validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Domain Verifier
3
+ *
4
+ * Performs DNS TXT record lookups to confirm that a tenant controls a domain.
5
+ *
6
+ * Protocol:
7
+ * 1. Tenant claims a domain → receives a token.
8
+ * 2. Tenant adds TXT record: `_skybber-verify.{domain}` = `skybber-verify={token}`
9
+ * 3. POST …/verify → this module resolves the TXT record and checks for the token.
10
+ *
11
+ * Fail-closed: any DNS error (NXDOMAIN, timeout, network failure) returns
12
+ * { verified: false, reason: "DNS_ERROR" }. The token must match exactly.
13
+ */
14
+ export interface VerifyResult {
15
+ verified: true;
16
+ }
17
+ export interface VerifyFailure {
18
+ verified: false;
19
+ reason: "TOKEN_MISMATCH" | "NO_RECORDS" | "DNS_ERROR";
20
+ }
21
+ export type DnsVerifyOutcome = VerifyResult | VerifyFailure;
22
+ /**
23
+ * Resolves the TXT record at `_skybber-verify.{domain}` and checks whether
24
+ * any value equals `skybber-verify={token}` exactly.
25
+ *
26
+ * Uses `dns.promises.resolveTxt` which does not follow HTTP redirects —
27
+ * there is no SSRF vector here.
28
+ */
29
+ export declare function verifyDomainToken(domain: string, token: string): Promise<DnsVerifyOutcome>;
30
+ //# sourceMappingURL=domain-verifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-verifier.d.ts","sourceRoot":"","sources":["../../../src/lib/tenant/domain-verifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,IAAI,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,KAAK,CAAC;IAChB,MAAM,EAAE,gBAAgB,GAAG,YAAY,GAAG,WAAW,CAAC;CACvD;AAED,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG,aAAa,CAAC;AAI5D;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,gBAAgB,CAAC,CA6B3B"}
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ /**
3
+ * Domain Verifier
4
+ *
5
+ * Performs DNS TXT record lookups to confirm that a tenant controls a domain.
6
+ *
7
+ * Protocol:
8
+ * 1. Tenant claims a domain → receives a token.
9
+ * 2. Tenant adds TXT record: `_skybber-verify.{domain}` = `skybber-verify={token}`
10
+ * 3. POST …/verify → this module resolves the TXT record and checks for the token.
11
+ *
12
+ * Fail-closed: any DNS error (NXDOMAIN, timeout, network failure) returns
13
+ * { verified: false, reason: "DNS_ERROR" }. The token must match exactly.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.verifyDomainToken = verifyDomainToken;
17
+ const node_dns_1 = require("node:dns");
18
+ const TXT_PREFIX = "_skybber-verify.";
19
+ /**
20
+ * Resolves the TXT record at `_skybber-verify.{domain}` and checks whether
21
+ * any value equals `skybber-verify={token}` exactly.
22
+ *
23
+ * Uses `dns.promises.resolveTxt` which does not follow HTTP redirects —
24
+ * there is no SSRF vector here.
25
+ */
26
+ async function verifyDomainToken(domain, token) {
27
+ const lookupName = `${TXT_PREFIX}${domain}`;
28
+ const expected = `skybber-verify=${token}`;
29
+ let txtRecords;
30
+ try {
31
+ txtRecords = await node_dns_1.promises.resolveTxt(lookupName);
32
+ }
33
+ catch (err) {
34
+ const code = err.code;
35
+ if (code === "ENODATA" || code === "ENOTFOUND") {
36
+ return { verified: false, reason: "NO_RECORDS" };
37
+ }
38
+ return { verified: false, reason: "DNS_ERROR" };
39
+ }
40
+ if (!txtRecords || txtRecords.length === 0) {
41
+ return { verified: false, reason: "NO_RECORDS" };
42
+ }
43
+ // TXT records are returned as arrays of strings (chunks). Join each record's
44
+ // chunks and compare against the expected value.
45
+ for (const chunks of txtRecords) {
46
+ const value = chunks.join("");
47
+ if (value === expected) {
48
+ return { verified: true };
49
+ }
50
+ }
51
+ return { verified: false, reason: "TOKEN_MISMATCH" };
52
+ }
53
+ //# sourceMappingURL=domain-verifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-verifier.js","sourceRoot":"","sources":["../../../src/lib/tenant/domain-verifier.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;AAwBH,8CAgCC;AAtDD,uCAA2C;AAa3C,MAAM,UAAU,GAAG,kBAAkB,CAAC;AAEtC;;;;;;GAMG;AACI,KAAK,UAAU,iBAAiB,CACrC,MAAc,EACd,KAAa;IAEb,MAAM,UAAU,GAAG,GAAG,UAAU,GAAG,MAAM,EAAE,CAAC;IAC5C,MAAM,QAAQ,GAAG,kBAAkB,KAAK,EAAE,CAAC;IAE3C,IAAI,UAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,UAAU,GAAG,MAAM,mBAAG,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAC;QACjD,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;YAC/C,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;QACnD,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAClD,CAAC;IAED,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3C,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IACnD,CAAC;IAED,6EAA6E;IAC7E,iDAAiD;IACjD,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;YACvB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;AACvD,CAAC"}