hazo_auth 7.0.1 → 8.0.1

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 (274) hide show
  1. package/README.md +96 -319
  2. package/SETUP_CHECKLIST.md +59 -248
  3. package/cli-src/cli/generate.ts +1 -10
  4. package/cli-src/cli/validate.ts +0 -4
  5. package/cli-src/lib/auth/auth_types.ts +15 -21
  6. package/cli-src/lib/auth/hazo_get_auth.server.ts +19 -0
  7. package/cli-src/lib/auth/hazo_get_tenant_auth.server.ts +24 -25
  8. package/cli-src/lib/auth/index.ts +2 -2
  9. package/cli-src/lib/auth/nextauth_config.ts +27 -67
  10. package/cli-src/lib/auth/with_auth.server.ts +15 -15
  11. package/cli-src/lib/config/default_config.ts +8 -0
  12. package/cli-src/lib/cookies_config.server.ts +1 -1
  13. package/cli-src/lib/email_verification_config.server.ts +34 -0
  14. package/cli-src/lib/forgot_password_config.server.ts +34 -0
  15. package/cli-src/lib/legal/legal_docs_config.server.ts +61 -0
  16. package/cli-src/lib/legal/legal_docs_reader.server.ts +36 -0
  17. package/cli-src/lib/legal/legal_docs_service.ts +196 -0
  18. package/cli-src/lib/legal/legal_docs_types.ts +31 -0
  19. package/cli-src/lib/login_config.server.ts +29 -14
  20. package/cli-src/lib/my_settings_config.server.ts +3 -0
  21. package/cli-src/lib/oauth_config.server.ts +31 -57
  22. package/cli-src/lib/register_config.server.ts +35 -11
  23. package/cli-src/lib/reset_password_config.server.ts +31 -0
  24. package/cli-src/lib/services/email_template_manifest.ts +0 -17
  25. package/cli-src/lib/services/index.ts +2 -8
  26. package/cli-src/lib/services/oauth_service.ts +74 -128
  27. package/cli-src/lib/services/otp_service.ts +7 -2
  28. package/cli-src/lib/services/registration_service.ts +16 -1
  29. package/cli-src/lib/services/session_token_service.ts +0 -2
  30. package/config/hazo_auth_config.example.ini +41 -76
  31. package/dist/cli/generate.d.ts.map +1 -1
  32. package/dist/cli/generate.js +1 -10
  33. package/dist/cli/validate.d.ts.map +1 -1
  34. package/dist/cli/validate.js +0 -4
  35. package/dist/client.d.ts +1 -2
  36. package/dist/client.d.ts.map +1 -1
  37. package/dist/client.js +3 -1
  38. package/dist/components/layouts/create_firm/index.d.ts +8 -4
  39. package/dist/components/layouts/create_firm/index.d.ts.map +1 -1
  40. package/dist/components/layouts/create_firm/index.js +3 -3
  41. package/dist/components/layouts/email_verification/index.d.ts +5 -4
  42. package/dist/components/layouts/email_verification/index.d.ts.map +1 -1
  43. package/dist/components/layouts/email_verification/index.js +4 -4
  44. package/dist/components/layouts/forgot_password/index.d.ts +5 -4
  45. package/dist/components/layouts/forgot_password/index.d.ts.map +1 -1
  46. package/dist/components/layouts/forgot_password/index.js +2 -2
  47. package/dist/components/layouts/index.d.ts +1 -0
  48. package/dist/components/layouts/index.d.ts.map +1 -1
  49. package/dist/components/layouts/index.js +2 -0
  50. package/dist/components/layouts/legal/index.d.ts +5 -0
  51. package/dist/components/layouts/legal/index.d.ts.map +1 -0
  52. package/dist/components/layouts/legal/index.js +4 -0
  53. package/dist/components/layouts/legal/legal_acceptance_gate.d.ts +7 -0
  54. package/dist/components/layouts/legal/legal_acceptance_gate.d.ts.map +1 -0
  55. package/dist/components/layouts/legal/legal_acceptance_gate.js +84 -0
  56. package/dist/components/layouts/legal/legal_doc_checkbox_list.d.ts +9 -0
  57. package/dist/components/layouts/legal/legal_doc_checkbox_list.d.ts.map +1 -0
  58. package/dist/components/layouts/legal/legal_doc_checkbox_list.js +11 -0
  59. package/dist/components/layouts/legal/legal_doc_combined_view.d.ts +9 -0
  60. package/dist/components/layouts/legal/legal_doc_combined_view.d.ts.map +1 -0
  61. package/dist/components/layouts/legal/legal_doc_combined_view.js +11 -0
  62. package/dist/components/layouts/legal/legal_doc_drawer.d.ts +8 -0
  63. package/dist/components/layouts/legal/legal_doc_drawer.d.ts.map +1 -0
  64. package/dist/components/layouts/legal/legal_doc_drawer.js +55 -0
  65. package/dist/components/layouts/login/index.d.ts +13 -19
  66. package/dist/components/layouts/login/index.d.ts.map +1 -1
  67. package/dist/components/layouts/login/index.js +8 -11
  68. package/dist/components/layouts/otp/index.d.ts +5 -1
  69. package/dist/components/layouts/otp/index.d.ts.map +1 -1
  70. package/dist/components/layouts/otp/index.js +2 -2
  71. package/dist/components/layouts/register/hooks/use_register_form.d.ts +5 -1
  72. package/dist/components/layouts/register/hooks/use_register_form.d.ts.map +1 -1
  73. package/dist/components/layouts/register/hooks/use_register_form.js +25 -10
  74. package/dist/components/layouts/register/index.d.ts +11 -11
  75. package/dist/components/layouts/register/index.d.ts.map +1 -1
  76. package/dist/components/layouts/register/index.js +26 -7
  77. package/dist/components/layouts/reset_password/index.d.ts +5 -4
  78. package/dist/components/layouts/reset_password/index.d.ts.map +1 -1
  79. package/dist/components/layouts/reset_password/index.js +5 -5
  80. package/dist/components/layouts/shared/components/already_logged_in_guard.d.ts +5 -3
  81. package/dist/components/layouts/shared/components/already_logged_in_guard.d.ts.map +1 -1
  82. package/dist/components/layouts/shared/components/already_logged_in_guard.js +2 -2
  83. package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts +2 -6
  84. package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts.map +1 -1
  85. package/dist/components/layouts/shared/components/facebook_sign_in_button.js +11 -13
  86. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
  87. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +3 -8
  88. package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts +6 -3
  89. package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts.map +1 -1
  90. package/dist/components/layouts/shared/components/two_column_auth_layout.js +5 -8
  91. package/dist/components/layouts/shared/index.d.ts +2 -0
  92. package/dist/components/layouts/shared/index.d.ts.map +1 -1
  93. package/dist/components/layouts/shared/index.js +1 -0
  94. package/dist/components/layouts/user_management/index.d.ts.map +1 -1
  95. package/dist/components/layouts/user_management/index.js +84 -9
  96. package/dist/components/ui/button.d.ts +1 -1
  97. package/dist/components/ui/input-otp.d.ts +2 -2
  98. package/dist/components/ui/sheet.d.ts +1 -1
  99. package/dist/index.d.ts +2 -1
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/lib/auth/auth_types.d.ts +14 -13
  102. package/dist/lib/auth/auth_types.d.ts.map +1 -1
  103. package/dist/lib/auth/auth_types.js +0 -10
  104. package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
  105. package/dist/lib/auth/hazo_get_auth.server.js +19 -0
  106. package/dist/lib/auth/hazo_get_tenant_auth.server.d.ts +7 -8
  107. package/dist/lib/auth/hazo_get_tenant_auth.server.d.ts.map +1 -1
  108. package/dist/lib/auth/hazo_get_tenant_auth.server.js +22 -23
  109. package/dist/lib/auth/index.d.ts +2 -2
  110. package/dist/lib/auth/index.d.ts.map +1 -1
  111. package/dist/lib/auth/nextauth_config.d.ts +0 -10
  112. package/dist/lib/auth/nextauth_config.d.ts.map +1 -1
  113. package/dist/lib/auth/nextauth_config.js +23 -52
  114. package/dist/lib/auth/with_auth.server.d.ts +13 -13
  115. package/dist/lib/auth/with_auth.server.d.ts.map +1 -1
  116. package/dist/lib/auth/with_auth.server.js +2 -2
  117. package/dist/lib/config/default_config.d.ts +16 -0
  118. package/dist/lib/config/default_config.d.ts.map +1 -1
  119. package/dist/lib/config/default_config.js +8 -0
  120. package/dist/lib/cookies_config.server.d.ts +1 -1
  121. package/dist/lib/cookies_config.server.js +1 -1
  122. package/dist/lib/email_verification_config.server.d.ts +3 -0
  123. package/dist/lib/email_verification_config.server.d.ts.map +1 -1
  124. package/dist/lib/email_verification_config.server.js +15 -0
  125. package/dist/lib/forgot_password_config.server.d.ts +3 -0
  126. package/dist/lib/forgot_password_config.server.d.ts.map +1 -1
  127. package/dist/lib/forgot_password_config.server.js +15 -0
  128. package/dist/lib/legal/legal_docs_config.server.d.ts +22 -0
  129. package/dist/lib/legal/legal_docs_config.server.d.ts.map +1 -0
  130. package/dist/lib/legal/legal_docs_config.server.js +52 -0
  131. package/dist/lib/legal/legal_docs_reader.server.d.ts +15 -0
  132. package/dist/lib/legal/legal_docs_reader.server.d.ts.map +1 -0
  133. package/dist/lib/legal/legal_docs_reader.server.js +24 -0
  134. package/dist/lib/legal/legal_docs_service.d.ts +49 -0
  135. package/dist/lib/legal/legal_docs_service.d.ts.map +1 -0
  136. package/dist/lib/legal/legal_docs_service.js +140 -0
  137. package/dist/lib/legal/legal_docs_types.d.ts +25 -0
  138. package/dist/lib/legal/legal_docs_types.d.ts.map +1 -0
  139. package/dist/lib/legal/legal_docs_types.js +2 -0
  140. package/dist/lib/login_config.server.d.ts +3 -6
  141. package/dist/lib/login_config.server.d.ts.map +1 -1
  142. package/dist/lib/login_config.server.js +11 -7
  143. package/dist/lib/my_settings_config.server.d.ts +1 -0
  144. package/dist/lib/my_settings_config.server.d.ts.map +1 -1
  145. package/dist/lib/my_settings_config.server.js +2 -0
  146. package/dist/lib/oauth_config.server.d.ts +8 -17
  147. package/dist/lib/oauth_config.server.d.ts.map +1 -1
  148. package/dist/lib/oauth_config.server.js +10 -25
  149. package/dist/lib/register_config.server.d.ts +5 -2
  150. package/dist/lib/register_config.server.d.ts.map +1 -1
  151. package/dist/lib/register_config.server.js +15 -4
  152. package/dist/lib/reset_password_config.server.d.ts +3 -0
  153. package/dist/lib/reset_password_config.server.d.ts.map +1 -1
  154. package/dist/lib/reset_password_config.server.js +13 -0
  155. package/dist/lib/services/email_template_manifest.d.ts.map +1 -1
  156. package/dist/lib/services/email_template_manifest.js +0 -17
  157. package/dist/lib/services/index.d.ts +0 -2
  158. package/dist/lib/services/index.d.ts.map +1 -1
  159. package/dist/lib/services/index.js +0 -1
  160. package/dist/lib/services/oauth_service.d.ts +11 -22
  161. package/dist/lib/services/oauth_service.d.ts.map +1 -1
  162. package/dist/lib/services/oauth_service.js +63 -96
  163. package/dist/lib/services/otp_service.d.ts +1 -1
  164. package/dist/lib/services/otp_service.d.ts.map +1 -1
  165. package/dist/lib/services/otp_service.js +6 -1
  166. package/dist/lib/services/registration_service.d.ts +5 -0
  167. package/dist/lib/services/registration_service.d.ts.map +1 -1
  168. package/dist/lib/services/registration_service.js +6 -0
  169. package/dist/lib/services/session_token_service.d.ts +0 -2
  170. package/dist/lib/services/session_token_service.d.ts.map +1 -1
  171. package/dist/lib/services/session_token_service.js +0 -2
  172. package/dist/page_components/create_firm.d.ts +1 -13
  173. package/dist/page_components/create_firm.d.ts.map +1 -1
  174. package/dist/page_components/create_firm.js +6 -10
  175. package/dist/page_components/forgot_password.d.ts +4 -1
  176. package/dist/page_components/forgot_password.d.ts.map +1 -1
  177. package/dist/page_components/forgot_password.js +6 -2
  178. package/dist/page_components/index.d.ts +0 -5
  179. package/dist/page_components/index.d.ts.map +1 -1
  180. package/dist/page_components/index.js +0 -5
  181. package/dist/page_components/login.d.ts +4 -1
  182. package/dist/page_components/login.d.ts.map +1 -1
  183. package/dist/page_components/login.js +6 -2
  184. package/dist/page_components/register.d.ts +4 -1
  185. package/dist/page_components/register.d.ts.map +1 -1
  186. package/dist/page_components/register.js +6 -2
  187. package/dist/page_components/reset_password.d.ts +4 -1
  188. package/dist/page_components/reset_password.d.ts.map +1 -1
  189. package/dist/page_components/reset_password.js +6 -2
  190. package/dist/page_components/verify_email.d.ts +4 -1
  191. package/dist/page_components/verify_email.d.ts.map +1 -1
  192. package/dist/page_components/verify_email.js +6 -2
  193. package/dist/server/routes/assets.d.ts +8 -0
  194. package/dist/server/routes/assets.d.ts.map +1 -0
  195. package/dist/server/routes/assets.js +38 -0
  196. package/dist/server/routes/consent_me.d.ts +4 -0
  197. package/dist/server/routes/consent_me.d.ts.map +1 -0
  198. package/dist/server/routes/consent_me.js +15 -0
  199. package/dist/server/routes/index.d.ts +9 -4
  200. package/dist/server/routes/index.d.ts.map +1 -1
  201. package/dist/server/routes/index.js +13 -5
  202. package/dist/server/routes/legal_docs_accept.d.ts +3 -0
  203. package/dist/server/routes/legal_docs_accept.d.ts.map +1 -0
  204. package/dist/server/routes/legal_docs_accept.js +43 -0
  205. package/dist/server/routes/legal_docs_get.d.ts +3 -0
  206. package/dist/server/routes/legal_docs_get.d.ts.map +1 -0
  207. package/dist/server/routes/legal_docs_get.js +49 -0
  208. package/dist/server/routes/legal_docs_publish.d.ts +3 -0
  209. package/dist/server/routes/legal_docs_publish.d.ts.map +1 -0
  210. package/dist/server/routes/legal_docs_publish.js +35 -0
  211. package/dist/server/routes/me.d.ts.map +1 -1
  212. package/dist/server/routes/me.js +1 -43
  213. package/dist/server/routes/oauth_facebook_callback.d.ts +1 -1
  214. package/dist/server/routes/oauth_facebook_callback.d.ts.map +1 -1
  215. package/dist/server/routes/oauth_facebook_callback.js +8 -1
  216. package/dist/server/routes/oauth_google_callback.js +1 -1
  217. package/dist/server/routes/otp/verify.js +2 -2
  218. package/dist/server/routes/register.d.ts.map +1 -1
  219. package/dist/server/routes/register.js +26 -0
  220. package/dist/server/routes/strings_defaults.d.ts +4 -0
  221. package/dist/server/routes/strings_defaults.d.ts.map +1 -0
  222. package/dist/server/routes/strings_defaults.js +7 -0
  223. package/dist/server/routes/user_management_users.d.ts +11 -0
  224. package/dist/server/routes/user_management_users.d.ts.map +1 -1
  225. package/dist/server/routes/user_management_users.js +94 -0
  226. package/dist/server-lib.d.ts +0 -3
  227. package/dist/server-lib.d.ts.map +1 -1
  228. package/dist/server-lib.js +0 -2
  229. package/dist/server_pages/forgot_password.d.ts +18 -14
  230. package/dist/server_pages/forgot_password.d.ts.map +1 -1
  231. package/dist/server_pages/forgot_password.js +14 -12
  232. package/dist/server_pages/forgot_password_client_wrapper.d.ts +8 -7
  233. package/dist/server_pages/forgot_password_client_wrapper.d.ts.map +1 -1
  234. package/dist/server_pages/forgot_password_client_wrapper.js +2 -2
  235. package/dist/server_pages/index.d.ts +2 -0
  236. package/dist/server_pages/index.d.ts.map +1 -1
  237. package/dist/server_pages/index.js +1 -0
  238. package/dist/server_pages/login.d.ts +22 -23
  239. package/dist/server_pages/login.d.ts.map +1 -1
  240. package/dist/server_pages/login.js +27 -14
  241. package/dist/server_pages/login_client_wrapper.d.ts +9 -10
  242. package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
  243. package/dist/server_pages/login_client_wrapper.js +2 -2
  244. package/dist/server_pages/my_settings.d.ts +1 -3
  245. package/dist/server_pages/my_settings.d.ts.map +1 -1
  246. package/dist/server_pages/my_settings.js +2 -9
  247. package/dist/server_pages/register.d.ts +17 -20
  248. package/dist/server_pages/register.d.ts.map +1 -1
  249. package/dist/server_pages/register.js +20 -15
  250. package/dist/server_pages/register_client_wrapper.d.ts +8 -10
  251. package/dist/server_pages/register_client_wrapper.d.ts.map +1 -1
  252. package/dist/server_pages/register_client_wrapper.js +2 -2
  253. package/dist/server_pages/reset_password.d.ts +16 -11
  254. package/dist/server_pages/reset_password.d.ts.map +1 -1
  255. package/dist/server_pages/reset_password.js +14 -10
  256. package/dist/server_pages/reset_password_client_wrapper.d.ts +8 -7
  257. package/dist/server_pages/reset_password_client_wrapper.d.ts.map +1 -1
  258. package/dist/server_pages/reset_password_client_wrapper.js +2 -2
  259. package/dist/server_pages/verify_email.d.ts +18 -12
  260. package/dist/server_pages/verify_email.d.ts.map +1 -1
  261. package/dist/server_pages/verify_email.js +13 -11
  262. package/dist/server_pages/verify_email_client_wrapper.d.ts +8 -7
  263. package/dist/server_pages/verify_email_client_wrapper.d.ts.map +1 -1
  264. package/dist/server_pages/verify_email_client_wrapper.js +2 -2
  265. package/dist/strings.d.ts +2 -0
  266. package/dist/strings.d.ts.map +1 -0
  267. package/dist/strings.js +3 -0
  268. package/dist/themes/index.d.ts +0 -1
  269. package/dist/themes/index.d.ts.map +1 -1
  270. package/dist/themes/index.js +1 -1
  271. package/package.json +30 -61
  272. package/dist/themes/preset_indigo_sunset.d.ts +0 -3
  273. package/dist/themes/preset_indigo_sunset.d.ts.map +0 -1
  274. package/dist/themes/preset_indigo_sunset.js +0 -20
@@ -0,0 +1,196 @@
1
+ // file_description: service functions for the legal document acceptance system
2
+ // section: imports
3
+ import { createCrudService } from 'hazo_connect/server';
4
+ import type { LegalAcceptanceMap } from './legal_docs_types';
5
+
6
+ // section: helpers
7
+
8
+ function generate_id(): string {
9
+ return crypto.randomUUID();
10
+ }
11
+
12
+ // section: exports
13
+
14
+ /**
15
+ * Write legal acceptance records. Call from:
16
+ * - registration_service (bundled with register POST, pre-session)
17
+ * - legal_docs_accept route (authenticated, post-login)
18
+ *
19
+ * Inserts one row per doc into hazo_legal_acceptances (audit history) and
20
+ * merges the result into the denormalised hazo_users.legal_acceptance JSONB
21
+ * column for fast "has this user accepted the current version?" queries.
22
+ */
23
+ export async function write_legal_acceptance(
24
+ adapter: any,
25
+ user_id: string,
26
+ accepted: Record<string, { hash: string }>,
27
+ ip: string | null,
28
+ user_agent: string | null,
29
+ ): Promise<void> {
30
+ const now = new Date().toISOString();
31
+
32
+ // 1. Insert one history row per doc
33
+ const history_service = createCrudService(adapter, 'hazo_legal_acceptances');
34
+ for (const [doc_key, { hash }] of Object.entries(accepted)) {
35
+ await history_service.insert({
36
+ id: generate_id(),
37
+ user_id,
38
+ doc_key,
39
+ doc_hash: hash,
40
+ accepted_at: now,
41
+ ip,
42
+ user_agent,
43
+ });
44
+ }
45
+
46
+ // 2. Merge into denormalized JSONB on hazo_users
47
+ const users_service = createCrudService(adapter, 'hazo_users');
48
+ const rows = await users_service.findBy({ id: user_id });
49
+
50
+ const existing_raw = rows[0]?.legal_acceptance;
51
+ let existing: LegalAcceptanceMap = {};
52
+ if (existing_raw) {
53
+ try {
54
+ existing = typeof existing_raw === 'string'
55
+ ? JSON.parse(existing_raw)
56
+ : (existing_raw as LegalAcceptanceMap);
57
+ } catch { /* corrupt — start fresh */ }
58
+ }
59
+
60
+ const updated: LegalAcceptanceMap = { ...existing };
61
+ for (const [doc_key, { hash }] of Object.entries(accepted)) {
62
+ updated[doc_key] = { hash, accepted_at: now, ip, user_agent };
63
+ }
64
+
65
+ await users_service.updateById(user_id, {
66
+ legal_acceptance: JSON.stringify(updated),
67
+ changed_at: now,
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Publish a doc version as the new required version (admin action).
73
+ * Upserts hazo_legal_doc_versions keyed on doc_key.
74
+ */
75
+ export async function publish_doc_version(
76
+ adapter: any,
77
+ doc_key: string,
78
+ required_hash: string,
79
+ published_by_user_id: string,
80
+ ): Promise<{ published_at: string }> {
81
+ const now = new Date().toISOString();
82
+
83
+ // createCrudService with doc_key as primary key so updateById works correctly
84
+ const versions_service = createCrudService(adapter, 'hazo_legal_doc_versions', {
85
+ primaryKeys: ['doc_key'],
86
+ autoId: false,
87
+ });
88
+
89
+ const existing = await versions_service.findBy({ doc_key });
90
+
91
+ if (existing.length > 0) {
92
+ await versions_service.updateById(doc_key, {
93
+ required_hash,
94
+ published_at: now,
95
+ published_by_user_id,
96
+ });
97
+ } else {
98
+ await versions_service.insert({
99
+ doc_key,
100
+ required_hash,
101
+ published_at: now,
102
+ published_by_user_id,
103
+ });
104
+ }
105
+
106
+ return { published_at: now };
107
+ }
108
+
109
+ /**
110
+ * Return required version info keyed by doc_key.
111
+ */
112
+ export async function get_required_versions(
113
+ adapter: any,
114
+ doc_keys: string[],
115
+ ): Promise<Record<string, { required_hash: string; published_at: string }>> {
116
+ if (doc_keys.length === 0) return {};
117
+
118
+ const versions_service = createCrudService(adapter, 'hazo_legal_doc_versions', {
119
+ primaryKeys: ['doc_key'],
120
+ autoId: false,
121
+ });
122
+
123
+ const rows = await versions_service.list((qb) =>
124
+ qb.whereIn('doc_key', doc_keys).select(['doc_key', 'required_hash', 'published_at'])
125
+ );
126
+
127
+ const result: Record<string, { required_hash: string; published_at: string }> = {};
128
+ for (const row of rows) {
129
+ result[String(row.doc_key)] = {
130
+ required_hash: String(row.required_hash),
131
+ published_at: String(row.published_at),
132
+ };
133
+ }
134
+ return result;
135
+ }
136
+
137
+ /**
138
+ * Return acceptance history for a user across all doc keys, newest first.
139
+ */
140
+ export async function get_user_acceptance_history(
141
+ adapter: any,
142
+ user_id: string,
143
+ ): Promise<Array<{
144
+ doc_key: string;
145
+ doc_hash: string;
146
+ accepted_at: string;
147
+ ip: string | null;
148
+ user_agent: string | null;
149
+ }>> {
150
+ const history_service = createCrudService(adapter, 'hazo_legal_acceptances');
151
+
152
+ return history_service.list((qb) =>
153
+ qb
154
+ .where('user_id', 'eq', user_id)
155
+ .select(['doc_key', 'doc_hash', 'accepted_at', 'ip', 'user_agent'])
156
+ .order('accepted_at', 'desc')
157
+ ) as Promise<Array<{
158
+ doc_key: string;
159
+ doc_hash: string;
160
+ accepted_at: string;
161
+ ip: string | null;
162
+ user_agent: string | null;
163
+ }>>;
164
+ }
165
+
166
+ /**
167
+ * Count how many users have accepted the current required hash for a doc key.
168
+ * Returns { current, total } where current is users on the required_hash and
169
+ * total is all users in the system (including those with no legal_acceptance data).
170
+ *
171
+ * Note: This performs an in-process scan over all users. For large user bases
172
+ * consider a dedicated SQL query in a future optimisation.
173
+ */
174
+ export async function get_compliance_count(
175
+ adapter: any,
176
+ doc_key: string,
177
+ required_hash: string,
178
+ ): Promise<{ current: number; total: number }> {
179
+ const users_service = createCrudService(adapter, 'hazo_users');
180
+ const all_users = await users_service.list((qb) =>
181
+ qb.select(['id', 'legal_acceptance'])
182
+ );
183
+
184
+ let current = 0;
185
+ for (const user of all_users) {
186
+ let map: LegalAcceptanceMap = {};
187
+ try {
188
+ map = typeof user.legal_acceptance === 'string'
189
+ ? JSON.parse(user.legal_acceptance as string)
190
+ : ((user.legal_acceptance ?? {}) as LegalAcceptanceMap);
191
+ } catch { /* ignore corrupt rows */ }
192
+ if (map[doc_key]?.hash === required_hash) current++;
193
+ }
194
+
195
+ return { current, total: all_users.length };
196
+ }
@@ -0,0 +1,31 @@
1
+ // file_description: shared types for the legal document acceptance system
2
+
3
+ export interface LegalDocConfig {
4
+ key: string;
5
+ title: string;
6
+ path: string;
7
+ }
8
+
9
+ export interface LegalDocsConfig {
10
+ docs: LegalDocConfig[];
11
+ display_mode: 'separate' | 'combined';
12
+ }
13
+
14
+ export interface LegalDoc {
15
+ key: string;
16
+ title: string;
17
+ content: string;
18
+ hash: string;
19
+ required_hash: string | null;
20
+ required_published_at: string | null;
21
+ }
22
+
23
+ export interface LegalAcceptanceRecord {
24
+ hash: string;
25
+ accepted_at: string;
26
+ ip: string | null;
27
+ user_agent: string | null;
28
+ }
29
+
30
+ // Shape of hazo_users.legal_acceptance JSONB
31
+ export type LegalAcceptanceMap = Record<string, LegalAcceptanceRecord>;
@@ -7,6 +7,9 @@ import { get_config_value, get_config_value_allow_empty } from "./config/config_
7
7
  import { get_already_logged_in_config } from "./already_logged_in_config.server.js";
8
8
  import { get_oauth_config, type OAuthConfig } from "./oauth_config.server.js";
9
9
 
10
+ // Assets served via the package's own API route — no manual copy needed.
11
+ const DEFAULT_LOGIN_IMAGE_PATH = "/api/hazo_auth/assets/login_default.jpg";
12
+
10
13
  // section: types
11
14
  export type LoginConfig = {
12
15
  redirectRoute?: string;
@@ -21,14 +24,11 @@ export type LoginConfig = {
21
24
  createAccountPath: string;
22
25
  createAccountLabel: string;
23
26
  showCreateAccountLink: boolean;
27
+ imageSrc: string;
28
+ imageAlt: string;
29
+ imageBackgroundColor: string;
24
30
  /** OAuth configuration */
25
31
  oauth: OAuthConfig;
26
- /** Whether the OTP sign-in link is shown below the login form */
27
- otpSigninEnabled: boolean;
28
- /** Label for the OTP sign-in link */
29
- otpSigninLabel: string;
30
- /** href for the OTP sign-in link */
31
- otpSigninHref: string;
32
32
  };
33
33
 
34
34
  // section: helpers
@@ -69,14 +69,29 @@ export function get_login_config(): LoginConfig {
69
69
  // Get shared already logged in config
70
70
  const alreadyLoggedInConfig = get_already_logged_in_config();
71
71
 
72
+ // Read image configuration
73
+ // If not set in config, falls back to default path-based image
74
+ // Consuming apps should copy images to public/hazo_auth/images/ or configure their own image_src
75
+ const imageSrc = get_config_value(
76
+ section,
77
+ "image_src",
78
+ DEFAULT_LOGIN_IMAGE_PATH
79
+ );
80
+
81
+ const imageAlt = get_config_value(
82
+ section,
83
+ "image_alt",
84
+ "Secure login illustration"
85
+ );
86
+ const imageBackgroundColor = get_config_value(
87
+ section,
88
+ "image_background_color",
89
+ "#f1f5f9"
90
+ );
91
+
72
92
  // Get OAuth configuration
73
93
  const oauth = get_oauth_config();
74
94
 
75
- // OTP sign-in link
76
- const otpSigninEnabled = get_config_value(section, "otp_signin_enabled", "false") === "true";
77
- const otpSigninLabel = get_config_value(section, "otp_signin_label", "Sign in with email code");
78
- const otpSigninHref = get_config_value(section, "otp_signin_href", "/hazo_auth/otp");
79
-
80
95
  return {
81
96
  redirectRoute,
82
97
  successMessage,
@@ -90,10 +105,10 @@ export function get_login_config(): LoginConfig {
90
105
  createAccountPath,
91
106
  createAccountLabel,
92
107
  showCreateAccountLink,
108
+ imageSrc,
109
+ imageAlt,
110
+ imageBackgroundColor,
93
111
  oauth,
94
- otpSigninEnabled,
95
- otpSigninLabel,
96
- otpSigninHref,
97
112
  };
98
113
  }
99
114
 
@@ -34,6 +34,7 @@ export type MySettingsConfig = {
34
34
  user_photo_default_priority2?: "library" | "gravatar";
35
35
  library_photo_path: string;
36
36
  };
37
+ heading?: string;
37
38
  subHeading?: string;
38
39
  profilePhotoLabel?: string;
39
40
  profilePhotoRecommendation?: string;
@@ -94,6 +95,7 @@ export function get_my_settings_config(): MySettingsConfig {
94
95
  const fileTypes = get_file_types_config();
95
96
 
96
97
  // Read optional labels with defaults
98
+ const heading = get_config_value(section, "heading", "Account Settings");
97
99
  const subHeading = get_config_value(section, "sub_heading", "Manage your profile, password, and email preferences.");
98
100
  const profilePhotoLabel = get_config_value(section, "profile_photo_label", "Profile Photo");
99
101
  const profilePhotoRecommendation = get_config_value(section, "profile_photo_recommendation", "Recommended size: 200x200px. JPG, PNG.");
@@ -113,6 +115,7 @@ export function get_my_settings_config(): MySettingsConfig {
113
115
  userFields,
114
116
  passwordRequirements,
115
117
  profilePicture,
118
+ heading,
116
119
  subHeading,
117
120
  profilePhotoLabel,
118
121
  profilePhotoRecommendation,
@@ -10,20 +10,12 @@ import { DEFAULT_OAUTH } from "./config/default_config.js";
10
10
  export type OAuthConfig = {
11
11
  /** Enable Google OAuth login */
12
12
  enable_google: boolean;
13
- /** Enable Facebook OAuth login */
14
- enable_facebook: boolean;
15
13
  /** Enable traditional email/password login */
16
14
  enable_email_password: boolean;
17
15
  /** Auto-link Google login to existing unverified email/password accounts */
18
16
  auto_link_unverified_accounts: boolean;
19
- /** Auto-link Google login to existing unverified email/password accounts (per-provider override) */
20
- auto_link_unverified_accounts_google: boolean;
21
- /** Auto-link Facebook login to existing unverified email/password accounts */
22
- auto_link_unverified_accounts_facebook: boolean;
23
17
  /** Text displayed on the Google sign-in button */
24
18
  google_button_text: string;
25
- /** Text displayed on the Facebook sign-in button */
26
- facebook_button_text: string;
27
19
  /** Text displayed on the divider between OAuth and email/password form */
28
20
  oauth_divider_text: string;
29
21
  /** NextAuth signIn page path */
@@ -38,10 +30,14 @@ export type OAuthConfig = {
38
30
  skip_invitation_check: boolean;
39
31
  /** Redirect when skip_invitation_check=true and user has no scope */
40
32
  no_scope_redirect: string;
41
- /** Facebook App ID from env HAZO_AUTH_FACEBOOK_APP_ID */
42
- facebook_client_id: string | undefined;
43
- /** Facebook App Secret from env HAZO_AUTH_FACEBOOK_APP_SECRET */
44
- facebook_client_secret: string | undefined;
33
+ /** Enable Facebook OAuth login */
34
+ enable_facebook_oauth: boolean;
35
+ /** Facebook App ID (env: HAZO_AUTH_FACEBOOK_APP_ID overrides config) */
36
+ facebook_app_id: string;
37
+ /** Facebook App Secret (env: HAZO_AUTH_FACEBOOK_APP_SECRET overrides config) */
38
+ facebook_app_secret: string;
39
+ /** Text displayed on the Facebook sign-in button */
40
+ facebook_button_text: string;
45
41
  };
46
42
 
47
43
  // section: constants
@@ -60,51 +56,24 @@ export function get_oauth_config(): OAuthConfig {
60
56
  DEFAULT_OAUTH.enable_google
61
57
  );
62
58
 
63
- const enable_facebook = get_config_boolean(
64
- SECTION_NAME,
65
- "enable_facebook",
66
- false
67
- );
68
-
69
59
  const enable_email_password = get_config_boolean(
70
60
  SECTION_NAME,
71
61
  "enable_email_password",
72
62
  DEFAULT_OAUTH.enable_email_password
73
63
  );
74
64
 
75
- // Generic key (backward compat)
76
65
  const auto_link_unverified_accounts = get_config_boolean(
77
66
  SECTION_NAME,
78
67
  "auto_link_unverified_accounts",
79
68
  DEFAULT_OAUTH.auto_link_unverified_accounts
80
69
  );
81
70
 
82
- // Per-provider Google key (falls back to generic)
83
- const auto_link_unverified_accounts_google = get_config_boolean(
84
- SECTION_NAME,
85
- "auto_link_unverified_accounts_google",
86
- auto_link_unverified_accounts
87
- );
88
-
89
- // Per-provider Facebook key (defaults to false — don't auto-link Facebook unverified)
90
- const auto_link_unverified_accounts_facebook = get_config_boolean(
91
- SECTION_NAME,
92
- "auto_link_unverified_accounts_facebook",
93
- false
94
- );
95
-
96
71
  const google_button_text = get_config_value(
97
72
  SECTION_NAME,
98
73
  "google_button_text",
99
74
  DEFAULT_OAUTH.google_button_text
100
75
  );
101
76
 
102
- const facebook_button_text = get_config_value(
103
- SECTION_NAME,
104
- "facebook_button_text",
105
- "Continue with Facebook"
106
- );
107
-
108
77
  const oauth_divider_text = get_config_value(
109
78
  SECTION_NAME,
110
79
  "oauth_divider_text",
@@ -147,18 +116,31 @@ export function get_oauth_config(): OAuthConfig {
147
116
  DEFAULT_OAUTH.no_scope_redirect
148
117
  );
149
118
 
150
- const facebook_client_id = process.env.HAZO_AUTH_FACEBOOK_APP_ID;
151
- const facebook_client_secret = process.env.HAZO_AUTH_FACEBOOK_APP_SECRET;
119
+ const enable_facebook_oauth = get_config_boolean(
120
+ SECTION_NAME,
121
+ "enable_facebook_oauth",
122
+ false
123
+ );
124
+
125
+ const facebook_app_id =
126
+ process.env.HAZO_AUTH_FACEBOOK_APP_ID ||
127
+ get_config_value(SECTION_NAME, "facebook_app_id", "");
128
+
129
+ const facebook_app_secret =
130
+ process.env.HAZO_AUTH_FACEBOOK_APP_SECRET ||
131
+ get_config_value(SECTION_NAME, "facebook_app_secret", "");
132
+
133
+ const facebook_button_text = get_config_value(
134
+ SECTION_NAME,
135
+ "facebook_button_text",
136
+ "Continue with Facebook"
137
+ );
152
138
 
153
139
  return {
154
140
  enable_google,
155
- enable_facebook,
156
141
  enable_email_password,
157
142
  auto_link_unverified_accounts,
158
- auto_link_unverified_accounts_google,
159
- auto_link_unverified_accounts_facebook,
160
143
  google_button_text,
161
- facebook_button_text,
162
144
  oauth_divider_text,
163
145
  sign_in_page,
164
146
  error_page,
@@ -166,8 +148,10 @@ export function get_oauth_config(): OAuthConfig {
166
148
  default_redirect,
167
149
  skip_invitation_check,
168
150
  no_scope_redirect,
169
- facebook_client_id,
170
- facebook_client_secret,
151
+ enable_facebook_oauth,
152
+ facebook_app_id,
153
+ facebook_app_secret,
154
+ facebook_button_text,
171
155
  };
172
156
  }
173
157
 
@@ -190,13 +174,3 @@ export function is_email_password_enabled(): boolean {
190
174
  DEFAULT_OAUTH.enable_email_password
191
175
  );
192
176
  }
193
-
194
- /**
195
- * Helper to check if Facebook OAuth is enabled and credentials are present
196
- * @returns true if Facebook OAuth is enabled and env vars are set
197
- */
198
- export function is_facebook_oauth_enabled(): boolean {
199
- const enabled = get_config_boolean(SECTION_NAME, "enable_facebook", false);
200
- if (!enabled) return false;
201
- return !!(process.env.HAZO_AUTH_FACEBOOK_APP_ID && process.env.HAZO_AUTH_FACEBOOK_APP_SECRET);
202
- }
@@ -9,6 +9,11 @@ import { get_already_logged_in_config } from "./already_logged_in_config.server.
9
9
  import { get_user_fields_config } from "./user_fields_config.server.js";
10
10
  import { get_oauth_config } from "./oauth_config.server.js";
11
11
 
12
+ // Default image path - consuming apps should either:
13
+ // 1. Configure their own image_src in hazo_auth_config.ini
14
+ // 2. Copy the default images from node_modules/hazo_auth/public/hazo_auth/images/ to their public folder
15
+ const DEFAULT_REGISTER_IMAGE_PATH = "/hazo_auth/images/register_default.jpg";
16
+
12
17
  // section: types
13
18
  export type RegisterConfig = {
14
19
  showNameField: boolean;
@@ -26,14 +31,17 @@ export type RegisterConfig = {
26
31
  returnHomePath: string;
27
32
  signInPath: string;
28
33
  signInLabel: string;
34
+ imageSrc: string;
35
+ imageAlt: string;
36
+ imageBackgroundColor: string;
29
37
  /** OAuth configuration */
30
38
  oauth: {
31
39
  enable_google: boolean;
32
- enable_facebook: boolean;
33
40
  enable_email_password: boolean;
34
41
  google_button_text: string;
35
- facebook_button_text: string;
36
42
  oauth_divider_text: string;
43
+ enable_facebook_oauth: boolean;
44
+ facebook_button_text: string;
37
45
  };
38
46
  };
39
47
 
@@ -71,6 +79,26 @@ export function get_register_config(): RegisterConfig {
71
79
  "Sign in"
72
80
  );
73
81
 
82
+ // Read image configuration
83
+ // If not set in config, falls back to default path-based image
84
+ // Consuming apps should copy images to public/hazo_auth/images/ or configure their own image_src
85
+ const imageSrc = get_config_value(
86
+ "hazo_auth__register_layout",
87
+ "image_src",
88
+ DEFAULT_REGISTER_IMAGE_PATH
89
+ );
90
+
91
+ const imageAlt = get_config_value(
92
+ "hazo_auth__register_layout",
93
+ "image_alt",
94
+ "Modern building representing user registration"
95
+ );
96
+ const imageBackgroundColor = get_config_value(
97
+ "hazo_auth__register_layout",
98
+ "image_background_color",
99
+ "#e2e8f0"
100
+ );
101
+
74
102
  // Get OAuth configuration (shared with login)
75
103
  const oauthConfig = get_oauth_config();
76
104
 
@@ -81,13 +109,6 @@ export function get_register_config(): RegisterConfig {
81
109
  "Sign up with Google"
82
110
  );
83
111
 
84
- // For the register page, default Facebook button text to "Sign up with Facebook" unless overridden
85
- const registerFacebookButtonText = get_config_value(
86
- "hazo_auth__oauth",
87
- "facebook_button_text_register",
88
- "Sign up with Facebook"
89
- );
90
-
91
112
  return {
92
113
  showNameField,
93
114
  passwordRequirements,
@@ -98,13 +119,16 @@ export function get_register_config(): RegisterConfig {
98
119
  returnHomePath: alreadyLoggedInConfig.returnHomePath,
99
120
  signInPath,
100
121
  signInLabel,
122
+ imageSrc,
123
+ imageAlt,
124
+ imageBackgroundColor,
101
125
  oauth: {
102
126
  enable_google: oauthConfig.enable_google,
103
- enable_facebook: oauthConfig.enable_facebook,
104
127
  enable_email_password: oauthConfig.enable_email_password,
105
128
  google_button_text: registerGoogleButtonText,
106
- facebook_button_text: registerFacebookButtonText,
107
129
  oauth_divider_text: oauthConfig.oauth_divider_text,
130
+ enable_facebook_oauth: oauthConfig.enable_facebook_oauth,
131
+ facebook_button_text: oauthConfig.facebook_button_text,
108
132
  },
109
133
  };
110
134
  }
@@ -7,6 +7,11 @@ import { get_config_value } from "./config/config_loader.server.js";
7
7
  import { get_already_logged_in_config } from "./already_logged_in_config.server.js";
8
8
  import { get_password_requirements_config } from "./password_requirements_config.server.js";
9
9
 
10
+ // Default image path - consuming apps should either:
11
+ // 1. Configure their own image_src in hazo_auth_config.ini
12
+ // 2. Copy the default images from node_modules/hazo_auth/public/hazo_auth/images/ to their public folder
13
+ const DEFAULT_RESET_PASSWORD_IMAGE_PATH = "/hazo_auth/images/reset_password_default.jpg";
14
+
10
15
  // section: types
11
16
  export type ResetPasswordConfig = {
12
17
  errorMessage: string;
@@ -25,6 +30,9 @@ export type ResetPasswordConfig = {
25
30
  require_number: boolean;
26
31
  require_special: boolean;
27
32
  };
33
+ imageSrc: string;
34
+ imageAlt: string;
35
+ imageBackgroundColor: string;
28
36
  };
29
37
 
30
38
  // section: helpers
@@ -62,6 +70,26 @@ export function get_reset_password_config(): ResetPasswordConfig {
62
70
  // Get shared password requirements
63
71
  const passwordRequirements = get_password_requirements_config();
64
72
 
73
+ // Read image configuration
74
+ // If not set in config, falls back to default path-based image
75
+ // Consuming apps should copy images to public/hazo_auth/images/ or configure their own image_src
76
+ const imageSrc = get_config_value(
77
+ section,
78
+ "image_src",
79
+ DEFAULT_RESET_PASSWORD_IMAGE_PATH
80
+ );
81
+
82
+ const imageAlt = get_config_value(
83
+ section,
84
+ "image_alt",
85
+ "Reset password illustration"
86
+ );
87
+ const imageBackgroundColor = get_config_value(
88
+ section,
89
+ "image_background_color",
90
+ "#f1f5f9"
91
+ );
92
+
65
93
  return {
66
94
  errorMessage,
67
95
  successMessage,
@@ -73,6 +101,9 @@ export function get_reset_password_config(): ResetPasswordConfig {
73
101
  returnHomeButtonLabel: alreadyLoggedInConfig.returnHomeButtonLabel,
74
102
  returnHomePath: alreadyLoggedInConfig.returnHomePath,
75
103
  passwordRequirements,
104
+ imageSrc,
105
+ imageAlt,
106
+ imageBackgroundColor,
76
107
  };
77
108
  }
78
109
 
@@ -101,21 +101,4 @@ export const hazo_auth_template_manifest: SystemTemplateManifest[] = [
101
101
  },
102
102
  ],
103
103
  },
104
- {
105
- template_name: "otp_signin_code",
106
- template_label: "OTP sign-in code",
107
- category: SYSTEM_CATEGORY,
108
- html: read_template("otp_signin_code", "html"),
109
- text: read_template("otp_signin_code", "txt"),
110
- variables: [
111
- {
112
- variable_name: "otp_code",
113
- variable_description: "6-digit OTP code for email sign-in (v6.1.0+)",
114
- },
115
- {
116
- variable_name: "expires_in_minutes",
117
- variable_description: "Number of minutes until the OTP code expires",
118
- },
119
- ],
120
- },
121
104
  ];
@@ -20,11 +20,5 @@ export * from "./scope_service.js";
20
20
  export * from "./user_scope_service.js";
21
21
  export * from "./oauth_service.js";
22
22
  export * from "./branding_service.js";
23
- export {
24
- request_email_otp,
25
- verify_email_otp,
26
- generate_otp_code,
27
- hash_otp_code,
28
- verify_otp_code,
29
- } from "./otp_service.js";
30
- export type { RequestEmailOTPResult, VerifyEmailOTPResult } from "./otp_service";
23
+
24
+