hazo_auth 6.0.0 → 6.1.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 (90) hide show
  1. package/README.md +70 -0
  2. package/SETUP_CHECKLIST.md +92 -0
  3. package/cli-src/cli/validate.ts +4 -0
  4. package/cli-src/lib/cookies_config.server.ts +1 -0
  5. package/cli-src/lib/login_config.server.ts +14 -0
  6. package/cli-src/lib/otp_config.server.ts +91 -0
  7. package/cli-src/lib/services/email_service.ts +3 -1
  8. package/cli-src/lib/services/email_template_manifest.ts +17 -0
  9. package/cli-src/lib/services/email_templates/otp_signin_code.html +13 -0
  10. package/cli-src/lib/services/email_templates/otp_signin_code.txt +5 -0
  11. package/cli-src/lib/services/index.ts +8 -2
  12. package/cli-src/lib/services/otp_service.ts +295 -0
  13. package/cli-src/lib/services/session_token_service.ts +4 -1
  14. package/config/hazo_auth_config.example.ini +38 -0
  15. package/dist/cli/validate.d.ts.map +1 -1
  16. package/dist/cli/validate.js +4 -0
  17. package/dist/client.d.ts +2 -0
  18. package/dist/client.d.ts.map +1 -1
  19. package/dist/client.js +1 -0
  20. package/dist/components/layouts/login/index.d.ts +7 -1
  21. package/dist/components/layouts/login/index.d.ts.map +1 -1
  22. package/dist/components/layouts/login/index.js +2 -2
  23. package/dist/components/layouts/otp/index.d.ts +10 -0
  24. package/dist/components/layouts/otp/index.d.ts.map +1 -0
  25. package/dist/components/layouts/otp/index.js +14 -0
  26. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
  27. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +8 -3
  28. package/dist/components/otp/OTPRequestForm.d.ts +11 -0
  29. package/dist/components/otp/OTPRequestForm.d.ts.map +1 -0
  30. package/dist/components/otp/OTPRequestForm.js +42 -0
  31. package/dist/components/otp/OTPVerifyForm.d.ts +16 -0
  32. package/dist/components/otp/OTPVerifyForm.d.ts.map +1 -0
  33. package/dist/components/otp/OTPVerifyForm.js +75 -0
  34. package/dist/components/otp/index.d.ts +5 -0
  35. package/dist/components/otp/index.d.ts.map +1 -0
  36. package/dist/components/otp/index.js +2 -0
  37. package/dist/components/ui/input-otp.d.ts +35 -0
  38. package/dist/components/ui/input-otp.d.ts.map +1 -0
  39. package/dist/components/ui/input-otp.js +44 -0
  40. package/dist/lib/cookies_config.server.d.ts +1 -0
  41. package/dist/lib/cookies_config.server.d.ts.map +1 -1
  42. package/dist/lib/cookies_config.server.js +1 -0
  43. package/dist/lib/login_config.server.d.ts +6 -0
  44. package/dist/lib/login_config.server.d.ts.map +1 -1
  45. package/dist/lib/login_config.server.js +7 -0
  46. package/dist/lib/otp_config.server.d.ts +49 -0
  47. package/dist/lib/otp_config.server.d.ts.map +1 -0
  48. package/dist/lib/otp_config.server.js +48 -0
  49. package/dist/lib/services/email_service.d.ts +1 -1
  50. package/dist/lib/services/email_service.d.ts.map +1 -1
  51. package/dist/lib/services/email_service.js +2 -0
  52. package/dist/lib/services/email_template_manifest.d.ts.map +1 -1
  53. package/dist/lib/services/email_template_manifest.js +17 -0
  54. package/dist/lib/services/email_templates/otp_signin_code.html +13 -0
  55. package/dist/lib/services/email_templates/otp_signin_code.txt +5 -0
  56. package/dist/lib/services/index.d.ts +2 -0
  57. package/dist/lib/services/index.d.ts.map +1 -1
  58. package/dist/lib/services/index.js +1 -0
  59. package/dist/lib/services/otp_service.d.ts +46 -0
  60. package/dist/lib/services/otp_service.d.ts.map +1 -0
  61. package/dist/lib/services/otp_service.js +238 -0
  62. package/dist/lib/services/session_token_service.d.ts +3 -1
  63. package/dist/lib/services/session_token_service.d.ts.map +1 -1
  64. package/dist/lib/services/session_token_service.js +4 -2
  65. package/dist/page_components/otp.d.ts +4 -0
  66. package/dist/page_components/otp.d.ts.map +1 -0
  67. package/dist/page_components/otp.js +5 -0
  68. package/dist/server/routes/index.d.ts +2 -0
  69. package/dist/server/routes/index.d.ts.map +1 -1
  70. package/dist/server/routes/index.js +3 -0
  71. package/dist/server/routes/me.d.ts.map +1 -1
  72. package/dist/server/routes/me.js +43 -1
  73. package/dist/server/routes/otp/request.d.ts +3 -0
  74. package/dist/server/routes/otp/request.d.ts.map +1 -0
  75. package/dist/server/routes/otp/request.js +33 -0
  76. package/dist/server/routes/otp/verify.d.ts +3 -0
  77. package/dist/server/routes/otp/verify.d.ts.map +1 -0
  78. package/dist/server/routes/otp/verify.js +58 -0
  79. package/dist/server-lib.d.ts +3 -0
  80. package/dist/server-lib.d.ts.map +1 -1
  81. package/dist/server-lib.js +2 -0
  82. package/dist/server_pages/login.d.ts.map +1 -1
  83. package/dist/server_pages/login.js +1 -1
  84. package/dist/server_pages/login_client_wrapper.d.ts +1 -1
  85. package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
  86. package/dist/server_pages/login_client_wrapper.js +2 -2
  87. package/dist/server_pages/otp.d.ts +42 -0
  88. package/dist/server_pages/otp.d.ts.map +1 -0
  89. package/dist/server_pages/otp.js +38 -0
  90. package/package.json +18 -1
@@ -0,0 +1,295 @@
1
+ import "server-only";
2
+ import crypto from "node:crypto";
3
+ import argon2 from "argon2";
4
+ import { createCrudService } from "hazo_connect/server";
5
+ import { get_otp_config, hazo_auth_otp_session_ttl_seconds } from "../otp_config.server.js";
6
+ import { get_hazo_connect_instance } from "../hazo_connect_instance.server.js";
7
+ import { send_template_email } from "./email_service.js";
8
+ import { create_app_logger } from "../app_logger.js";
9
+ import { create_session_token } from "./session_token_service.js";
10
+
11
+ /**
12
+ * Generates a cryptographically random 6-digit numeric OTP code (000000–999999).
13
+ * Uses crypto.randomInt for uniform distribution.
14
+ */
15
+ export function generate_otp_code(): string {
16
+ const n = crypto.randomInt(0, 1_000_000);
17
+ return String(n).padStart(6, "0");
18
+ }
19
+
20
+ export async function hash_otp_code(code: string): Promise<string> {
21
+ return argon2.hash(code);
22
+ }
23
+
24
+ export async function verify_otp_code(otp_hash: string, code: string): Promise<boolean> {
25
+ try {
26
+ return await argon2.verify(otp_hash, code);
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ // section: types
33
+
34
+ export type RequestEmailOTPResult =
35
+ | { ok: true }
36
+ | { ok: false; error: "rate_limited"; retry_after_seconds: number };
37
+
38
+ // section: request_email_otp
39
+
40
+ /**
41
+ * Initiates an OTP sign-in flow for the given email address.
42
+ *
43
+ * Behaviour:
44
+ * 1. Per-email rate limit — rejects if too many requests in the sliding window.
45
+ * 2. Per-IP rate limit — rejects if too many requests from this IP.
46
+ * 3. Unknown email + auto_register=false — silent no-op (constant-time padding).
47
+ * 4. Unknown email + auto_register=true — inserts OTP row with user_id=null, dispatches email.
48
+ * 5. Known email — marks prior unconsumed rows consumed, inserts fresh OTP row, dispatches email.
49
+ *
50
+ * Never reveals whether an email address is registered (always returns ok:true on success).
51
+ */
52
+ export async function request_email_otp(args: {
53
+ email: string;
54
+ ip: string;
55
+ }): Promise<RequestEmailOTPResult> {
56
+ const logger = create_app_logger();
57
+ const cfg = get_otp_config();
58
+ const email = args.email.trim().toLowerCase();
59
+ const ip = args.ip;
60
+
61
+ const adapter = get_hazo_connect_instance();
62
+ const otp_table = createCrudService(adapter, "hazo_email_otps");
63
+ const users_table = createCrudService(adapter, "hazo_users");
64
+
65
+ // 1. Per-email rate limit
66
+ const email_window_ms = cfg.email_rate_limit_window_seconds * 1000;
67
+ const email_threshold = new Date(Date.now() - email_window_ms).toISOString();
68
+ const recent_for_email = await otp_table.list((qb) =>
69
+ qb
70
+ .select(["created_at"])
71
+ .where("email", "eq", email)
72
+ .where("created_at", "gte", email_threshold)
73
+ );
74
+ if (recent_for_email.length >= cfg.email_rate_limit_max) {
75
+ const oldest = recent_for_email
76
+ .map((r) => Date.parse(String(r.created_at)))
77
+ .sort((a, b) => a - b)[0];
78
+ const retry_after_seconds = Math.max(
79
+ 1,
80
+ Math.ceil((oldest + email_window_ms - Date.now()) / 1000),
81
+ );
82
+ logger.warn("otp_request_email_rate_limited", { email, ip, retry_after_seconds });
83
+ return { ok: false, error: "rate_limited", retry_after_seconds };
84
+ }
85
+
86
+ // 2. Per-IP rate limit
87
+ const ip_window_ms = cfg.ip_rate_limit_window_seconds * 1000;
88
+ const ip_threshold = new Date(Date.now() - ip_window_ms).toISOString();
89
+ const recent_for_ip = await otp_table.list((qb) =>
90
+ qb
91
+ .select(["created_at"])
92
+ .where("requester_ip", "eq", ip)
93
+ .where("created_at", "gte", ip_threshold)
94
+ );
95
+ if (recent_for_ip.length >= cfg.ip_rate_limit_max) {
96
+ const oldest = recent_for_ip
97
+ .map((r) => Date.parse(String(r.created_at)))
98
+ .sort((a, b) => a - b)[0];
99
+ const retry_after_seconds = Math.max(
100
+ 1,
101
+ Math.ceil((oldest + ip_window_ms - Date.now()) / 1000),
102
+ );
103
+ logger.warn("otp_request_ip_rate_limited", { email, ip, retry_after_seconds });
104
+ return { ok: false, error: "rate_limited", retry_after_seconds };
105
+ }
106
+
107
+ // 3. Lookup user
108
+ const existing_users = await users_table.findBy({ email_address: email });
109
+ const existing_user = existing_users.length > 0 ? existing_users[0] : null;
110
+
111
+ // 4. Unknown email + auto_register=false → silent no-op with constant-time padding
112
+ if (!existing_user && !cfg.auto_register) {
113
+ await argon2.hash("000000"); // constant-time padding — never stored
114
+ return { ok: true };
115
+ }
116
+
117
+ // 5. Mark any unconsumed rows for this email as superseded
118
+ try {
119
+ const unconsumed = await otp_table.list((qb) =>
120
+ qb
121
+ .select(["id"])
122
+ .where("email", "eq", email)
123
+ .where("consumed_at", "is", null)
124
+ );
125
+ for (const row of unconsumed) {
126
+ await otp_table.updateById(String(row.id), { consumed_at: new Date().toISOString() });
127
+ }
128
+ } catch {
129
+ // IS NULL filter may not be supported in all adapter versions — not critical
130
+ }
131
+
132
+ // 6. Generate code + hash + insert row
133
+ const code = generate_otp_code();
134
+ const otp_hash = await hash_otp_code(code);
135
+ const expires_at = new Date(Date.now() + cfg.code_ttl_seconds * 1000).toISOString();
136
+ const row_id = crypto.randomUUID();
137
+
138
+ await otp_table.insert({
139
+ id: row_id,
140
+ user_id: existing_user ? String(existing_user.id) : null,
141
+ email,
142
+ otp_hash,
143
+ expires_at,
144
+ attempt_count: 0,
145
+ requester_ip: ip,
146
+ });
147
+
148
+ // 7. Dispatch email — fire-and-forget; errors are logged but do not surface to caller
149
+ try {
150
+ await send_template_email("otp_signin_code", email, {
151
+ otp_code: code,
152
+ expires_in_minutes: String(Math.round(cfg.code_ttl_seconds / 60)),
153
+ });
154
+ } catch (err) {
155
+ logger.error("otp_request_email_dispatch_failed", {
156
+ email,
157
+ ip,
158
+ error: err instanceof Error ? err.message : String(err),
159
+ });
160
+ // Return ok:true to preserve no-enumeration property — caller cannot distinguish
161
+ // a missing user from a delivery failure.
162
+ }
163
+
164
+ return { ok: true };
165
+ }
166
+
167
+ // section: verify_email_otp
168
+
169
+ export type VerifyEmailOTPResult =
170
+ | { ok: true; user_id: string; email: string; session_token: string }
171
+ | { ok: false; error: "invalid_or_expired" };
172
+
173
+ export async function verify_email_otp(args: {
174
+ email: string;
175
+ code: string;
176
+ ip: string;
177
+ }): Promise<VerifyEmailOTPResult> {
178
+ const logger = create_app_logger();
179
+ const cfg = get_otp_config();
180
+ const email = args.email.trim().toLowerCase();
181
+ const code = args.code.trim();
182
+
183
+ const adapter = get_hazo_connect_instance();
184
+ const otp_table = createCrudService(adapter, "hazo_email_otps");
185
+ const users_table = createCrudService(adapter, "hazo_users");
186
+ const user_scopes_table = createCrudService(adapter, "hazo_user_scopes");
187
+ const roles_table = createCrudService(adapter, "hazo_roles");
188
+
189
+ // 1. Find most-recent unconsumed row for this email
190
+ const now_iso = new Date().toISOString();
191
+ const candidates = await otp_table.list((qb) =>
192
+ qb
193
+ .select(["id", "user_id", "otp_hash", "expires_at", "attempt_count"])
194
+ .where("email", "eq", email)
195
+ .where("consumed_at", "is", null)
196
+ .order("created_at", "desc")
197
+ .limit(1)
198
+ );
199
+ const row = candidates.length > 0 ? candidates[0] : null;
200
+
201
+ if (!row) {
202
+ return { ok: false, error: "invalid_or_expired" };
203
+ }
204
+
205
+ // 2. Check expiry
206
+ const expires_at_ms = Date.parse(String(row.expires_at));
207
+ if (Number.isNaN(expires_at_ms) || expires_at_ms < Date.now()) {
208
+ return { ok: false, error: "invalid_or_expired" };
209
+ }
210
+
211
+ // 3. argon2 verify
212
+ const is_valid = await verify_otp_code(String(row.otp_hash), code);
213
+ if (!is_valid) {
214
+ const new_attempt_count = Number(row.attempt_count) + 1;
215
+ const updates: Record<string, unknown> = { attempt_count: new_attempt_count };
216
+ if (new_attempt_count >= cfg.max_verify_attempts) {
217
+ updates.consumed_at = now_iso; // poison
218
+ }
219
+ await otp_table.updateById(String(row.id), updates);
220
+ logger.info("otp_verify_invalid_code", {
221
+ email,
222
+ attempt_count: new_attempt_count,
223
+ poisoned: new_attempt_count >= cfg.max_verify_attempts,
224
+ });
225
+ return { ok: false, error: "invalid_or_expired" };
226
+ }
227
+
228
+ // 4. Mark consumed
229
+ await otp_table.updateById(String(row.id), { consumed_at: now_iso });
230
+
231
+ // 5. Resolve / create user
232
+ let user_id: string | null = row.user_id ? String(row.user_id) : null;
233
+
234
+ if (user_id) {
235
+ // Ensure email_verified=true
236
+ const user = await users_table.findById(user_id);
237
+ if (user && !user.email_verified) {
238
+ await users_table.updateById(user_id, { email_verified: true });
239
+ }
240
+ } else if (cfg.auto_register) {
241
+ // Create user + bind scope/role
242
+ const new_user_id = crypto.randomUUID();
243
+ await users_table.insert({
244
+ id: new_user_id,
245
+ email_address: email,
246
+ email_verified: true,
247
+ password_hash: null,
248
+ name: null,
249
+ });
250
+
251
+ // Resolve role_id from name — try scope-specific first, then any
252
+ const scoped_roles = await roles_table.findBy({
253
+ name: cfg.auto_assign_role_name,
254
+ scope_id: cfg.auto_assign_scope_id,
255
+ });
256
+ let role_id: string | null = null;
257
+ if (scoped_roles.length > 0) {
258
+ role_id = String(scoped_roles[0].id);
259
+ } else {
260
+ const any_roles = await roles_table.findBy({ name: cfg.auto_assign_role_name });
261
+ if (any_roles.length > 0) {
262
+ role_id = String(any_roles[0].id);
263
+ }
264
+ }
265
+
266
+ if (!role_id) {
267
+ logger.error("otp_verify_auto_register_role_not_found", {
268
+ email,
269
+ scope_id: cfg.auto_assign_scope_id,
270
+ role_name: cfg.auto_assign_role_name,
271
+ });
272
+ return { ok: false, error: "invalid_or_expired" };
273
+ }
274
+
275
+ await user_scopes_table.insert({
276
+ user_id: new_user_id,
277
+ scope_id: cfg.auto_assign_scope_id,
278
+ root_scope_id: cfg.auto_assign_scope_id,
279
+ role_id,
280
+ });
281
+
282
+ user_id = new_user_id;
283
+ } else {
284
+ // Row exists but no user and auto_register=false — stale edge case
285
+ logger.warn("otp_verify_no_user_resolvable", { email });
286
+ return { ok: false, error: "invalid_or_expired" };
287
+ }
288
+
289
+ // 6. Issue session JWT with OTP TTL
290
+ const ttl_seconds = hazo_auth_otp_session_ttl_seconds();
291
+ const session_token = await create_session_token(user_id, email, undefined, ttl_seconds);
292
+
293
+ logger.info("otp_verify_ok", { email, user_id, ttl_seconds });
294
+ return { ok: true, user_id, email, session_token };
295
+ }
@@ -63,19 +63,22 @@ function get_session_token_expiry_seconds(): number {
63
63
  * Token includes user_id, email, issued at time, and expiration
64
64
  * @param user_id - User ID
65
65
  * @param email - User email address
66
+ * @param managed_by_user_id - Optional: ID of the managing user (for impersonation)
67
+ * @param ttl_seconds - Optional: token lifetime in seconds (default: 30 days). Use 604800 for 7-day OTP sessions.
66
68
  * @returns JWT token string
67
69
  */
68
70
  export async function create_session_token(
69
71
  user_id: string,
70
72
  email: string,
71
73
  managed_by_user_id?: string,
74
+ ttl_seconds?: number,
72
75
  ): Promise<string> {
73
76
  const logger = create_app_logger();
74
77
 
75
78
  try {
76
79
  const secret = get_jwt_secret();
77
80
  const now = Math.floor(Date.now() / 1000); // Current time in seconds
78
- const expiry_seconds = get_session_token_expiry_seconds();
81
+ const expiry_seconds = ttl_seconds ?? get_session_token_expiry_seconds();
79
82
  const exp = now + expiry_seconds;
80
83
 
81
84
  const payload: Record<string, unknown> = { user_id, email };
@@ -159,6 +159,11 @@ enable_admin_ui = true
159
159
  # Success message (shown when no redirect route is provided)
160
160
  # success_message = Successfully logged in
161
161
 
162
+ ; OTP sign-in link in the login layout.
163
+ otp_signin_enabled = false
164
+ otp_signin_label = Sign in with email code
165
+ otp_signin_href = /hazo_auth/otp
166
+
162
167
  [hazo_auth__forgot_password_layout]
163
168
  # Image configuration
164
169
  # image_src = /globe.svg
@@ -729,3 +734,36 @@ company_name = My Company
729
734
  # [hazo_auth__login_layout]
730
735
  # image_src = /your-custom-image.jpg
731
736
 
737
+ [hazo_auth__otp]
738
+ ; Email-OTP sign-in configuration (v6.1.0+).
739
+ ;
740
+ ; Whether /otp/verify may create new hazo_users rows for unknown emails.
741
+ ; false (default): /otp/request silently no-ops for unknown emails.
742
+ ; true: /otp/verify creates a user row on first successful code match.
743
+ otp_auto_register = false
744
+
745
+ ; OTP code lifetime in seconds (default 600 = 10 minutes).
746
+ otp_code_ttl_seconds = 600
747
+
748
+ ; OTP session lifetime in seconds (default 604800 = 7 days).
749
+ otp_session_ttl_seconds = 604800
750
+
751
+ ; Sliding-session threshold: if a /me call lands and the JWT exp is within
752
+ ; this many seconds, re-issue the session for another otp_session_ttl_seconds.
753
+ otp_slide_when_within_seconds = 86400
754
+
755
+ ; Per-email rate limit for /otp/request.
756
+ otp_email_rate_limit_max = 3
757
+ otp_email_rate_limit_window_seconds = 900
758
+
759
+ ; Per-IP rate limit for /otp/request.
760
+ otp_ip_rate_limit_max = 20
761
+ otp_ip_rate_limit_window_seconds = 3600
762
+
763
+ ; Wrong-guess limit per code; row is poisoned after this many failures.
764
+ otp_max_verify_attempts = 5
765
+
766
+ ; Scope binding for newly auto-registered users (only used when otp_auto_register=true).
767
+ otp_auto_assign_scope_id = 00000000-0000-0000-0000-000000000001
768
+ otp_auto_assign_role_name = member
769
+
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/cli/validate.ts"],"names":[],"mappings":"AAOA,KAAK,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB,CAAC;AA2rBF,wBAAgB,cAAc,IAAI,iBAAiB,CA2ElD"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/cli/validate.ts"],"names":[],"mappings":"AAOA,KAAK,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB,CAAC;AA+rBF,wBAAgB,cAAc,IAAI,iBAAiB,CA2ElD"}
@@ -44,6 +44,9 @@ const REQUIRED_API_ROUTES = [
44
44
  { path: "api/hazo_auth/user_management/users/roles", method: "GET" },
45
45
  { path: "api/hazo_auth/user_management/users/roles", method: "POST" },
46
46
  { path: "api/hazo_auth/user_management/users/roles", method: "PUT" },
47
+ // OTP routes
48
+ { path: "api/hazo_auth/otp/request", method: "POST" },
49
+ { path: "api/hazo_auth/otp/verify", method: "POST" },
47
50
  ];
48
51
  // section: helpers
49
52
  function get_project_root() {
@@ -486,6 +489,7 @@ const REQUIRED_TABLES = [
486
489
  "hazo_role_permissions",
487
490
  "hazo_invitations",
488
491
  "hazo_refresh_tokens",
492
+ "hazo_email_otps",
489
493
  ];
490
494
  const TEXT_ID_TABLES = [
491
495
  "hazo_users",
package/dist/client.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from "./components/index.js";
2
+ export { OTPRequestForm, OTPVerifyForm } from "./components/otp.js";
3
+ export type { OTPRequestFormProps, OTPVerifyFormProps } from "./components/otp";
2
4
  export { cn, merge_class_names } from "./lib/utils.js";
3
5
  export * from "./lib/auth/auth_types.js";
4
6
  export { use_auth_status, trigger_auth_status_refresh } from "./components/layouts/shared/hooks/use_auth_status.js";
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAYA,cAAc,oBAAoB,CAAC;AAInC,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIpD,cAAc,uBAAuB,CAAC;AAItC,OAAO,EAAE,eAAe,EAAE,2BAA2B,EAAE,MAAM,mDAAmD,CAAC;AACjH,OAAO,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,iDAAiD,CAAC;AAC3G,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,iDAAiD,CAAC;AAC7G,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,qDAAqD,CAAC;AACnH,YAAY,EAAE,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,qDAAqD,CAAC;AAGvI,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAI/E,cAAc,8CAA8C,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAYA,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjE,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAIhF,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIpD,cAAc,uBAAuB,CAAC;AAItC,OAAO,EAAE,eAAe,EAAE,2BAA2B,EAAE,MAAM,mDAAmD,CAAC;AACjH,OAAO,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,iDAAiD,CAAC;AAC3G,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,iDAAiD,CAAC;AAC7G,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,qDAAqD,CAAC;AACnH,YAAY,EAAE,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,qDAAqD,CAAC;AAGvI,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAI/E,cAAc,8CAA8C,CAAC"}
package/dist/client.js CHANGED
@@ -10,6 +10,7 @@
10
10
  // section: component_exports
11
11
  // All UI and layout components are client-safe
12
12
  export * from "./components/index.js";
13
+ export { OTPRequestForm, OTPVerifyForm } from "./components/otp.js";
13
14
  // section: utility_exports
14
15
  // CSS utility functions
15
16
  export { cn, merge_class_names } from "./lib/utils.js";
@@ -43,6 +43,12 @@ export type LoginLayoutProps<TClient = unknown> = {
43
43
  urlOnLogon?: string;
44
44
  /** OAuth configuration */
45
45
  oauth?: OAuthLayoutConfig;
46
+ /** Show the OTP sign-in link below the login form (default: false) */
47
+ otp_signin_enabled?: boolean;
48
+ /** Label for the OTP sign-in link */
49
+ otp_signin_label?: string;
50
+ /** href for the OTP sign-in link */
51
+ otp_signin_href?: string;
46
52
  /**
47
53
  * Layout mode (default: `"two_column"`).
48
54
  * - `"two_column"` — renders the form inside the package's TwoColumnAuthLayout
@@ -55,5 +61,5 @@ export type LoginLayoutProps<TClient = unknown> = {
55
61
  */
56
62
  layout?: "two_column" | "form_only";
57
63
  };
58
- export default function login_layout<TClient>({ image_src, image_alt, image_background_color, field_overrides, labels, button_colors, data_client, logger, redirectRoute, successMessage, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, forgot_password_path, forgot_password_label, create_account_path, create_account_label, show_create_account_link, urlOnLogon, oauth, layout, }: LoginLayoutProps<TClient>): import("react/jsx-runtime").JSX.Element;
64
+ export default function login_layout<TClient>({ image_src, image_alt, image_background_color, field_overrides, labels, button_colors, data_client, logger, redirectRoute, successMessage, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, forgot_password_path, forgot_password_label, create_account_path, create_account_label, show_create_account_link, urlOnLogon, oauth, otp_signin_enabled, otp_signin_label, otp_signin_href, layout, }: LoginLayoutProps<TClient>): import("react/jsx-runtime").JSX.Element;
59
65
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/login/index.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/login/index.tsx"],"names":[],"mappings":"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"}
@@ -22,7 +22,7 @@ const ORDERED_FIELDS = [
22
22
  LOGIN_FIELD_IDS.PASSWORD,
23
23
  ];
24
24
  // section: component
25
- export default function login_layout({ image_src, image_alt, image_background_color = "#f1f5f9", field_overrides, labels, button_colors, data_client, logger, redirectRoute, successMessage = "Successfully logged in", alreadyLoggedInMessage = "You are already logged in", showLogoutButton = true, showReturnHomeButton = false, returnHomeButtonLabel = "Return home", returnHomePath = "/", forgot_password_path = "/hazo_auth/forgot_password", forgot_password_label = "Forgot password?", create_account_path = "/hazo_auth/register", create_account_label = "Create account", show_create_account_link = true, urlOnLogon, oauth, layout = "two_column", }) {
25
+ export default function login_layout({ image_src, image_alt, image_background_color = "#f1f5f9", field_overrides, labels, button_colors, data_client, logger, redirectRoute, successMessage = "Successfully logged in", alreadyLoggedInMessage = "You are already logged in", showLogoutButton = true, showReturnHomeButton = false, returnHomeButtonLabel = "Return home", returnHomePath = "/", forgot_password_path = "/hazo_auth/forgot_password", forgot_password_label = "Forgot password?", create_account_path = "/hazo_auth/register", create_account_label = "Create account", show_create_account_link = true, urlOnLogon, oauth, otp_signin_enabled = false, otp_signin_label = "Sign in with email code", otp_signin_href = "/hazo_auth/otp", layout = "two_column", }) {
26
26
  var _a;
27
27
  // Default OAuth config: both enabled
28
28
  const oauthConfig = oauth || {
@@ -95,7 +95,7 @@ export default function login_layout({ image_src, image_alt, image_background_co
95
95
  // into TwoColumnAuthLayout's `formContent`; in "form_only" mode it's returned
96
96
  // as the entire output for the consumer to compose into their own chrome.
97
97
  const successContent = (_jsxs(_Fragment, { children: [_jsx(FormHeader, { heading: resolvedLabels.heading, subHeading: resolvedLabels.subHeading }), _jsxs("div", { className: "cls_login_layout_success flex flex-col items-center justify-center gap-4 p-8 text-center", children: [_jsx(CheckCircle, { className: "cls_login_layout_success_icon h-16 w-16 text-green-600", "aria-hidden": "true" }), _jsx("p", { className: "cls_login_layout_success_message text-lg font-medium text-slate-900", children: successMessage })] })] }));
98
- const mainContent = (_jsxs(_Fragment, { children: [_jsx(FormHeader, { heading: resolvedLabels.heading, subHeading: resolvedLabels.subHeading }), oauthError && (_jsxs("div", { className: "cls_login_layout_oauth_error flex items-center gap-2 rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700", children: [_jsx(AlertCircle, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }), _jsx("span", { children: getOAuthErrorMessage(oauthError) })] })), oauthConfig.enable_google && (_jsx("div", { className: "cls_login_layout_oauth_section", children: _jsx(GoogleSignInButton, { label: oauthConfig.google_button_text, callbackUrl: oauthCallbackUrl }) })), oauthConfig.enable_google && oauthConfig.enable_email_password && (_jsx(OAuthDivider, { text: oauthConfig.oauth_divider_text })), oauthConfig.enable_email_password && (_jsxs("form", { className: "cls_login_layout_form_fields flex flex-col gap-5", onSubmit: form.handleSubmit, "aria-label": "Login form", children: [renderFields(form), _jsx(FormActionButtons, { submitLabel: resolvedLabels.submitButton, cancelLabel: resolvedLabels.cancelButton, buttonPalette: resolvedButtonPalette, isSubmitDisabled: form.isSubmitDisabled, onCancel: form.handleCancel, submitAriaLabel: "Submit login form", cancelAriaLabel: "Cancel login form" }), ((forgot_password_path && forgot_password_label) || (show_create_account_link && create_account_path && create_account_label)) && (_jsxs("div", { className: "cls_login_layout_support_links flex flex-col gap-1 text-sm text-muted-foreground", children: [forgot_password_path && forgot_password_label && (_jsx(Link, { href: forgot_password_path, className: "cls_login_layout_forgot_password_link text-primary underline-offset-4 hover:underline", "aria-label": "Go to forgot password page", children: forgot_password_label })), show_create_account_link && create_account_path && create_account_label && (_jsx(Link, { href: create_account_path, className: "cls_login_layout_create_account_link text-primary underline-offset-4 hover:underline", "aria-label": "Go to create account page", children: create_account_label }))] }))] })), show_create_account_link && create_account_path && create_account_label && !oauthConfig.enable_email_password && oauthConfig.enable_google && (_jsx("div", { className: "cls_login_layout_support_links mt-4 text-center text-sm text-muted-foreground", children: _jsx(Link, { href: create_account_path, className: "cls_login_layout_create_account_link text-primary underline-offset-4 hover:underline", "aria-label": "Go to create account page", children: create_account_label }) }))] }));
98
+ const mainContent = (_jsxs(_Fragment, { children: [_jsx(FormHeader, { heading: resolvedLabels.heading, subHeading: resolvedLabels.subHeading }), oauthError && (_jsxs("div", { className: "cls_login_layout_oauth_error flex items-center gap-2 rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700", children: [_jsx(AlertCircle, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }), _jsx("span", { children: getOAuthErrorMessage(oauthError) })] })), oauthConfig.enable_google && (_jsx("div", { className: "cls_login_layout_oauth_section", children: _jsx(GoogleSignInButton, { label: oauthConfig.google_button_text, callbackUrl: oauthCallbackUrl }) })), oauthConfig.enable_google && oauthConfig.enable_email_password && (_jsx(OAuthDivider, { text: oauthConfig.oauth_divider_text })), oauthConfig.enable_email_password && (_jsxs("form", { className: "cls_login_layout_form_fields flex flex-col gap-5", onSubmit: form.handleSubmit, "aria-label": "Login form", children: [renderFields(form), _jsx(FormActionButtons, { submitLabel: resolvedLabels.submitButton, cancelLabel: resolvedLabels.cancelButton, buttonPalette: resolvedButtonPalette, isSubmitDisabled: form.isSubmitDisabled, onCancel: form.handleCancel, submitAriaLabel: "Submit login form", cancelAriaLabel: "Cancel login form" }), ((forgot_password_path && forgot_password_label) || (show_create_account_link && create_account_path && create_account_label)) && (_jsxs("div", { className: "cls_login_layout_support_links flex flex-col gap-1 text-sm text-muted-foreground", children: [forgot_password_path && forgot_password_label && (_jsx(Link, { href: forgot_password_path, className: "cls_login_layout_forgot_password_link text-primary underline-offset-4 hover:underline", "aria-label": "Go to forgot password page", children: forgot_password_label })), show_create_account_link && create_account_path && create_account_label && (_jsx(Link, { href: create_account_path, className: "cls_login_layout_create_account_link text-primary underline-offset-4 hover:underline", "aria-label": "Go to create account page", children: create_account_label }))] }))] })), show_create_account_link && create_account_path && create_account_label && !oauthConfig.enable_email_password && oauthConfig.enable_google && (_jsx("div", { className: "cls_login_layout_support_links mt-4 text-center text-sm text-muted-foreground", children: _jsx(Link, { href: create_account_path, className: "cls_login_layout_create_account_link text-primary underline-offset-4 hover:underline", "aria-label": "Go to create account page", children: create_account_label }) })), otp_signin_enabled && otp_signin_href && (_jsx("div", { className: "cls_login_layout_otp_link mt-4 text-center text-sm", children: _jsx("a", { href: otp_signin_href, className: "underline text-muted-foreground hover:text-foreground", "aria-label": "Sign in with email code", children: otp_signin_label !== null && otp_signin_label !== void 0 ? otp_signin_label : "Sign in with email code" }) }))] }));
99
99
  // Form-only mode: return the content directly. Consumer is expected to wrap
100
100
  // it in their own page/brand chrome. We deliberately skip AlreadyLoggedInGuard
101
101
  // here — its 2-col fallback would defeat the purpose of form_only. Consumer
@@ -0,0 +1,10 @@
1
+ import * as React from "react";
2
+ export interface OTPLayoutProps {
3
+ /** URL to redirect to after successful sign-in. Defaults to "/" */
4
+ redirect_url?: string;
5
+ /** Page heading. Defaults to "Sign in with email code" */
6
+ title?: string;
7
+ }
8
+ export declare function OTPLayout({ redirect_url, title, }: OTPLayoutProps): React.ReactElement;
9
+ export default OTPLayout;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/otp/index.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAGD,wBAAgB,SAAS,CAAC,EACxB,YAAkB,EAClB,KAAiC,GAClC,EAAE,cAAc,GAAG,KAAK,CAAC,YAAY,CAgBrC;AAED,eAAe,SAAS,CAAC"}
@@ -0,0 +1,14 @@
1
+ // file_description: OTP (email one-time-password) sign-in layout component
2
+ // section: client_directive
3
+ "use client";
4
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
+ // section: imports
6
+ import * as React from "react";
7
+ import { OTPRequestForm } from "../../otp/OTPRequestForm.js";
8
+ import { OTPVerifyForm } from "../../otp/OTPVerifyForm.js";
9
+ // section: component
10
+ export function OTPLayout({ redirect_url = "/", title = "Sign in with email code", }) {
11
+ const [sent_to, set_sent_to] = React.useState(null);
12
+ return (_jsxs("div", { className: "mx-auto w-full max-w-sm py-12", children: [_jsx("h1", { className: "text-2xl font-semibold mb-6", children: title }), sent_to ? (_jsx(OTPVerifyForm, { email: sent_to, redirect_url: redirect_url })) : (_jsx(OTPRequestForm, { on_sent: (email) => set_sent_to(email) }))] }));
13
+ }
14
+ export default OTPLayout;
@@ -1 +1 @@
1
- {"version":3,"file":"sidebar_layout_wrapper.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/sidebar_layout_wrapper.tsx"],"names":[],"mappings":"AAwBA,KAAK,yBAAyB,GAAG;IAC/B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAGF,wBAAgB,oBAAoB,CAAC,EAAE,QAAQ,EAAE,EAAE,yBAAyB,2CA+Q3E"}
1
+ {"version":3,"file":"sidebar_layout_wrapper.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/sidebar_layout_wrapper.tsx"],"names":[],"mappings":"AA6BA,KAAK,yBAAyB,GAAG;IAC/B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAGF,wBAAgB,oBAAoB,CAAC,EAAE,QAAQ,EAAE,EAAE,yBAAyB,2CA+S3E"}
@@ -4,12 +4,17 @@
4
4
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
5
  // section: imports
6
6
  import Link from "next/link";
7
- import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger, SidebarInset, } from "../../../ui/sidebar.js";
8
- import { LogIn, UserPlus, BookOpen, ExternalLink, Database, KeyRound, MailCheck, Key, User, ShieldCheck, CircleUserRound, FileJson, Building2, Palette, Settings2, Play } from "lucide-react";
7
+ import { usePathname } from "next/navigation";
8
+ import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarTrigger, SidebarInset, } from "../../../ui/sidebar.js";
9
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
10
+ import { LogIn, UserPlus, BookOpen, ExternalLink, Database, KeyRound, MailCheck, Key, User, ShieldCheck, CircleUserRound, FileJson, Building2, Palette, Settings2, Play, ChevronDown, LayoutGrid } from "lucide-react";
9
11
  import { use_auth_status } from "../hooks/use_auth_status.js";
10
12
  import { ProfilePicMenu } from "./profile_pic_menu.js";
11
13
  // section: component
12
14
  export function SidebarLayoutWrapper({ children }) {
13
15
  const authStatus = use_auth_status();
14
- return (_jsx(SidebarProvider, { children: _jsxs("div", { className: "cls_sidebar_layout_wrapper flex min-h-screen w-full", children: [_jsxs(Sidebar, { children: [_jsx(SidebarHeader, { className: "cls_sidebar_layout_header", children: _jsx("div", { className: "cls_sidebar_layout_title flex items-center gap-2 px-2 py-4", children: _jsx("h1", { className: "cls_sidebar_layout_title_text text-lg font-semibold text-sidebar-foreground", children: "hazo auth" }) }) }), _jsxs(SidebarContent, { className: "cls_sidebar_layout_content", children: [_jsxs(SidebarGroup, { className: "cls_sidebar_layout_test_group", children: [_jsx(SidebarGroupLabel, { className: "cls_sidebar_layout_group_label", children: "Test components" }), _jsxs(SidebarMenu, { className: "cls_sidebar_layout_test_menu", children: [_jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_login_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/login", className: "cls_sidebar_layout_test_login_link flex items-center gap-2", "aria-label": "Test login layout component", children: [_jsx(LogIn, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test login" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_register_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/register", className: "cls_sidebar_layout_test_register_link flex items-center gap-2", "aria-label": "Test register layout component", children: [_jsx(UserPlus, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test register" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_forgot_password_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/forgot_password", className: "cls_sidebar_layout_test_forgot_password_link flex items-center gap-2", "aria-label": "Test forgot password layout component", children: [_jsx(KeyRound, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test forgot password" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_reset_password_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/reset_password", className: "cls_sidebar_layout_test_reset_password_link flex items-center gap-2", "aria-label": "Test reset password layout component", children: [_jsx(Key, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test reset password" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_email_verification_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/verify_email", className: "cls_sidebar_layout_test_email_verification_link flex items-center gap-2", "aria-label": "Test email verification layout component", children: [_jsx(MailCheck, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test email verification" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_sqlite_admin_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_connect/sqlite_admin", className: "cls_sidebar_layout_sqlite_admin_link flex items-center gap-2", "aria-label": "Open SQLite admin UI to browse and edit database", children: [_jsx(Database, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "SQLite Admin" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_user_management_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/user_management", className: "cls_sidebar_layout_user_management_link flex items-center gap-2", "aria-label": "Open User Management to manage users, roles, and permissions", children: [_jsx(User, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "User Management" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_rbac_test_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/rbac_test", className: "cls_sidebar_layout_rbac_test_link flex items-center gap-2", "aria-label": "Test RBAC and HRBAC access control", children: [_jsx(ShieldCheck, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "RBAC/HRBAC Test" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_profile_stamp_test_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/profile_stamp_test", className: "cls_sidebar_layout_profile_stamp_test_link flex items-center gap-2", "aria-label": "Test ProfileStamp component", children: [_jsx(CircleUserRound, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "ProfileStamp Test" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_app_user_data_test_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/app_user_data_test", className: "cls_sidebar_layout_app_user_data_test_link flex items-center gap-2", "aria-label": "Test app_user_data JSON storage", children: [_jsx(FileJson, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "App User Data Test" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_create_firm_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/create_firm", className: "cls_sidebar_layout_create_firm_link flex items-center gap-2", "aria-label": "Test create firm flow", children: [_jsx(Building2, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Create Firm" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_edit_firm_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/edit_firm", className: "cls_sidebar_layout_edit_firm_link flex items-center gap-2", "aria-label": "Test branding editor for firm", children: [_jsx(Palette, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Edit Firm" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_relationships_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/relationships", className: "cls_sidebar_layout_relationships_link flex items-center gap-2", "aria-label": "Test relationship accounts", children: [_jsx(User, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Relationships" })] }) }) })] })] }), _jsxs(SidebarGroup, { className: "cls_sidebar_layout_testing_group", children: [_jsx(SidebarGroupLabel, { className: "cls_sidebar_layout_group_label", children: "Testing" }), _jsx(SidebarMenu, { className: "cls_sidebar_layout_testing_menu", children: _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_scenarios_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/test_scenarios", className: "cls_sidebar_layout_test_scenarios_link flex items-center gap-2", "aria-label": "Test scenarios", children: [_jsx(Settings2, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test Scenarios" })] }) }) }) })] }), _jsxs(SidebarGroup, { className: "cls_sidebar_layout_auto_test_group", children: [_jsx(SidebarGroupLabel, { className: "cls_sidebar_layout_group_label", children: "Auto Tests" }), _jsx(SidebarMenu, { className: "cls_sidebar_layout_auto_test_menu", children: _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_auto_test_runner_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/auto_test", className: "cls_sidebar_layout_auto_test_runner_link flex items-center gap-2", "aria-label": "Run automated tests", children: [_jsx(Play, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Auto Test Runner" })] }) }) }) })] }), _jsx(ProfilePicMenu, { variant: "sidebar", avatar_size: "sm", className: "cls_sidebar_layout_profile_menu", sidebar_group_label: "Account" }), _jsxs(SidebarGroup, { className: "cls_sidebar_layout_resources_group", children: [_jsx(SidebarGroupLabel, { className: "cls_sidebar_layout_group_label", children: "Resources" }), _jsxs(SidebarMenu, { className: "cls_sidebar_layout_resources_menu", children: [_jsx(SidebarMenuItem, { className: "cls_sidebar_layout_storybook_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs("a", { href: "http://localhost:6006", target: "_blank", rel: "noopener noreferrer", className: "cls_sidebar_layout_storybook_link flex items-center gap-2", "aria-label": "Open Storybook preview for reusable components", children: [_jsx(BookOpen, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Storybook" }), _jsx(ExternalLink, { className: "ml-auto h-3 w-3", "aria-hidden": "true" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_docs_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs("a", { href: "https://ui.shadcn.com/docs", target: "_blank", rel: "noopener noreferrer", className: "cls_sidebar_layout_docs_link flex items-center gap-2", "aria-label": "Review shadcn documentation for styling guidance", children: [_jsx(BookOpen, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Shadcn docs" }), _jsx(ExternalLink, { className: "ml-auto h-3 w-3", "aria-hidden": "true" })] }) }) })] })] })] })] }), _jsxs(SidebarInset, { className: "cls_sidebar_layout_inset", children: [_jsxs("header", { className: "cls_sidebar_layout_main_header flex h-16 shrink-0 items-center gap-2 border-b px-4", children: [_jsx(SidebarTrigger, { className: "cls_sidebar_layout_trigger" }), _jsx("div", { className: "cls_sidebar_layout_main_header_content flex flex-1 items-center gap-2", children: _jsx("h2", { className: "cls_sidebar_layout_main_title text-lg font-semibold text-foreground", children: "hazo reusable ui library workspace" }) }), _jsx(ProfilePicMenu, { className: "cls_sidebar_layout_auth_status", avatar_size: "sm" })] }), _jsx("main", { className: "cls_sidebar_layout_main_content flex flex-1 items-center justify-center p-6", children: children })] })] }) }));
16
+ const pathname = usePathname();
17
+ const login_paths = ["/hazo_auth/login", "/hazo_auth/login_variations"];
18
+ const login_submenu_open = login_paths.some((p) => pathname === null || pathname === void 0 ? void 0 : pathname.startsWith(p));
19
+ return (_jsx(SidebarProvider, { children: _jsxs("div", { className: "cls_sidebar_layout_wrapper flex min-h-screen w-full", children: [_jsxs(Sidebar, { children: [_jsx(SidebarHeader, { className: "cls_sidebar_layout_header", children: _jsx("div", { className: "cls_sidebar_layout_title flex items-center gap-2 px-2 py-4", children: _jsx("h1", { className: "cls_sidebar_layout_title_text text-lg font-semibold text-sidebar-foreground", children: "hazo auth" }) }) }), _jsxs(SidebarContent, { className: "cls_sidebar_layout_content", children: [_jsxs(SidebarGroup, { className: "cls_sidebar_layout_test_group", children: [_jsx(SidebarGroupLabel, { className: "cls_sidebar_layout_group_label", children: "Test components" }), _jsxs(SidebarMenu, { className: "cls_sidebar_layout_test_menu", children: [_jsx(SidebarMenuItem, { className: "cls_sidebar_layout_login_group_item", children: _jsxs(Collapsible, { defaultOpen: login_submenu_open, className: "group/login-collapsible w-full", children: [_jsx(CollapsibleTrigger, { asChild: true, children: _jsxs(SidebarMenuButton, { className: "cls_sidebar_layout_login_trigger flex items-center gap-2 w-full", "aria-label": "Toggle login submenu", children: [_jsx(LogIn, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Login" }), _jsx(ChevronDown, { className: "ml-auto h-4 w-4 transition-transform duration-200 group-data-[state=open]/login-collapsible:rotate-180", "aria-hidden": "true" })] }) }), _jsx(CollapsibleContent, { children: _jsxs(SidebarMenuSub, { className: "cls_sidebar_layout_login_submenu", children: [_jsx(SidebarMenuSubItem, { className: "cls_sidebar_layout_login_test_item", children: _jsx(SidebarMenuSubButton, { asChild: true, children: _jsx(Link, { href: "/hazo_auth/login", className: "cls_sidebar_layout_login_test_link flex items-center gap-2", "aria-label": "Test login layout component", children: _jsx("span", { children: "Test login" }) }) }) }), _jsx(SidebarMenuSubItem, { className: "cls_sidebar_layout_login_variations_item", children: _jsx(SidebarMenuSubButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/login_variations", className: "cls_sidebar_layout_login_variations_link flex items-center gap-2", "aria-label": "View login page variations", children: [_jsx(LayoutGrid, { className: "h-3.5 w-3.5", "aria-hidden": "true" }), _jsx("span", { children: "Variations" })] }) }) })] }) })] }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_register_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/register", className: "cls_sidebar_layout_test_register_link flex items-center gap-2", "aria-label": "Test register layout component", children: [_jsx(UserPlus, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test register" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_forgot_password_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/forgot_password", className: "cls_sidebar_layout_test_forgot_password_link flex items-center gap-2", "aria-label": "Test forgot password layout component", children: [_jsx(KeyRound, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test forgot password" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_reset_password_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/reset_password", className: "cls_sidebar_layout_test_reset_password_link flex items-center gap-2", "aria-label": "Test reset password layout component", children: [_jsx(Key, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test reset password" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_email_verification_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/verify_email", className: "cls_sidebar_layout_test_email_verification_link flex items-center gap-2", "aria-label": "Test email verification layout component", children: [_jsx(MailCheck, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test email verification" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_sqlite_admin_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_connect/sqlite_admin", className: "cls_sidebar_layout_sqlite_admin_link flex items-center gap-2", "aria-label": "Open SQLite admin UI to browse and edit database", children: [_jsx(Database, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "SQLite Admin" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_user_management_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/user_management", className: "cls_sidebar_layout_user_management_link flex items-center gap-2", "aria-label": "Open User Management to manage users, roles, and permissions", children: [_jsx(User, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "User Management" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_rbac_test_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/rbac_test", className: "cls_sidebar_layout_rbac_test_link flex items-center gap-2", "aria-label": "Test RBAC and HRBAC access control", children: [_jsx(ShieldCheck, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "RBAC/HRBAC Test" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_profile_stamp_test_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/profile_stamp_test", className: "cls_sidebar_layout_profile_stamp_test_link flex items-center gap-2", "aria-label": "Test ProfileStamp component", children: [_jsx(CircleUserRound, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "ProfileStamp Test" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_app_user_data_test_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/app_user_data_test", className: "cls_sidebar_layout_app_user_data_test_link flex items-center gap-2", "aria-label": "Test app_user_data JSON storage", children: [_jsx(FileJson, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "App User Data Test" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_create_firm_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/create_firm", className: "cls_sidebar_layout_create_firm_link flex items-center gap-2", "aria-label": "Test create firm flow", children: [_jsx(Building2, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Create Firm" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_edit_firm_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/edit_firm", className: "cls_sidebar_layout_edit_firm_link flex items-center gap-2", "aria-label": "Test branding editor for firm", children: [_jsx(Palette, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Edit Firm" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_relationships_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/relationships", className: "cls_sidebar_layout_relationships_link flex items-center gap-2", "aria-label": "Test relationship accounts", children: [_jsx(User, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Relationships" })] }) }) })] })] }), _jsxs(SidebarGroup, { className: "cls_sidebar_layout_testing_group", children: [_jsx(SidebarGroupLabel, { className: "cls_sidebar_layout_group_label", children: "Testing" }), _jsx(SidebarMenu, { className: "cls_sidebar_layout_testing_menu", children: _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_scenarios_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/test_scenarios", className: "cls_sidebar_layout_test_scenarios_link flex items-center gap-2", "aria-label": "Test scenarios", children: [_jsx(Settings2, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test Scenarios" })] }) }) }) })] }), _jsxs(SidebarGroup, { className: "cls_sidebar_layout_auto_test_group", children: [_jsx(SidebarGroupLabel, { className: "cls_sidebar_layout_group_label", children: "Auto Tests" }), _jsx(SidebarMenu, { className: "cls_sidebar_layout_auto_test_menu", children: _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_auto_test_runner_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/auto_test", className: "cls_sidebar_layout_auto_test_runner_link flex items-center gap-2", "aria-label": "Run automated tests", children: [_jsx(Play, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Auto Test Runner" })] }) }) }) })] }), _jsx(ProfilePicMenu, { variant: "sidebar", avatar_size: "sm", className: "cls_sidebar_layout_profile_menu", sidebar_group_label: "Account" }), _jsxs(SidebarGroup, { className: "cls_sidebar_layout_resources_group", children: [_jsx(SidebarGroupLabel, { className: "cls_sidebar_layout_group_label", children: "Resources" }), _jsxs(SidebarMenu, { className: "cls_sidebar_layout_resources_menu", children: [_jsx(SidebarMenuItem, { className: "cls_sidebar_layout_storybook_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs("a", { href: "http://localhost:6006", target: "_blank", rel: "noopener noreferrer", className: "cls_sidebar_layout_storybook_link flex items-center gap-2", "aria-label": "Open Storybook preview for reusable components", children: [_jsx(BookOpen, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Storybook" }), _jsx(ExternalLink, { className: "ml-auto h-3 w-3", "aria-hidden": "true" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_docs_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs("a", { href: "https://ui.shadcn.com/docs", target: "_blank", rel: "noopener noreferrer", className: "cls_sidebar_layout_docs_link flex items-center gap-2", "aria-label": "Review shadcn documentation for styling guidance", children: [_jsx(BookOpen, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Shadcn docs" }), _jsx(ExternalLink, { className: "ml-auto h-3 w-3", "aria-hidden": "true" })] }) }) })] })] })] })] }), _jsxs(SidebarInset, { className: "cls_sidebar_layout_inset", children: [_jsxs("header", { className: "cls_sidebar_layout_main_header flex h-16 shrink-0 items-center gap-2 border-b px-4", children: [_jsx(SidebarTrigger, { className: "cls_sidebar_layout_trigger" }), _jsx("div", { className: "cls_sidebar_layout_main_header_content flex flex-1 items-center gap-2", children: _jsx("h2", { className: "cls_sidebar_layout_main_title text-lg font-semibold text-foreground", children: "hazo reusable ui library workspace" }) }), _jsx(ProfilePicMenu, { className: "cls_sidebar_layout_auth_status", avatar_size: "sm" })] }), _jsx("main", { className: "cls_sidebar_layout_main_content flex flex-1 items-center justify-center p-6", children: children })] })] }) }));
15
20
  }
@@ -0,0 +1,11 @@
1
+ import * as React from "react";
2
+ export interface OTPRequestFormProps {
3
+ on_sent?: (email: string) => void;
4
+ api_base?: string;
5
+ email_label?: string;
6
+ submit_label?: string;
7
+ pending_label?: string;
8
+ className?: string;
9
+ }
10
+ export declare function OTPRequestForm({ on_sent, api_base, email_label, submit_label, pending_label, className, }: OTPRequestFormProps): React.ReactElement;
11
+ //# sourceMappingURL=OTPRequestForm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OTPRequestForm.d.ts","sourceRoot":"","sources":["../../../src/components/otp/OTPRequestForm.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,QAA2B,EAC3B,WAA6B,EAC7B,YAA0B,EAC1B,aAA0B,EAC1B,SAAS,GACV,EAAE,mBAAmB,GAAG,KAAK,CAAC,YAAY,CAsE1C"}