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.
- package/README.md +70 -0
- package/SETUP_CHECKLIST.md +92 -0
- package/cli-src/cli/validate.ts +4 -0
- package/cli-src/lib/cookies_config.server.ts +1 -0
- package/cli-src/lib/login_config.server.ts +14 -0
- package/cli-src/lib/otp_config.server.ts +91 -0
- package/cli-src/lib/services/email_service.ts +3 -1
- package/cli-src/lib/services/email_template_manifest.ts +17 -0
- package/cli-src/lib/services/email_templates/otp_signin_code.html +13 -0
- package/cli-src/lib/services/email_templates/otp_signin_code.txt +5 -0
- package/cli-src/lib/services/index.ts +8 -2
- package/cli-src/lib/services/otp_service.ts +295 -0
- package/cli-src/lib/services/session_token_service.ts +4 -1
- package/config/hazo_auth_config.example.ini +38 -0
- package/dist/cli/validate.d.ts.map +1 -1
- package/dist/cli/validate.js +4 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -0
- package/dist/components/layouts/login/index.d.ts +7 -1
- package/dist/components/layouts/login/index.d.ts.map +1 -1
- package/dist/components/layouts/login/index.js +2 -2
- package/dist/components/layouts/otp/index.d.ts +10 -0
- package/dist/components/layouts/otp/index.d.ts.map +1 -0
- package/dist/components/layouts/otp/index.js +14 -0
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +8 -3
- package/dist/components/otp/OTPRequestForm.d.ts +11 -0
- package/dist/components/otp/OTPRequestForm.d.ts.map +1 -0
- package/dist/components/otp/OTPRequestForm.js +42 -0
- package/dist/components/otp/OTPVerifyForm.d.ts +16 -0
- package/dist/components/otp/OTPVerifyForm.d.ts.map +1 -0
- package/dist/components/otp/OTPVerifyForm.js +75 -0
- package/dist/components/otp/index.d.ts +5 -0
- package/dist/components/otp/index.d.ts.map +1 -0
- package/dist/components/otp/index.js +2 -0
- package/dist/components/ui/input-otp.d.ts +35 -0
- package/dist/components/ui/input-otp.d.ts.map +1 -0
- package/dist/components/ui/input-otp.js +44 -0
- package/dist/lib/cookies_config.server.d.ts +1 -0
- package/dist/lib/cookies_config.server.d.ts.map +1 -1
- package/dist/lib/cookies_config.server.js +1 -0
- package/dist/lib/login_config.server.d.ts +6 -0
- package/dist/lib/login_config.server.d.ts.map +1 -1
- package/dist/lib/login_config.server.js +7 -0
- package/dist/lib/otp_config.server.d.ts +49 -0
- package/dist/lib/otp_config.server.d.ts.map +1 -0
- package/dist/lib/otp_config.server.js +48 -0
- package/dist/lib/services/email_service.d.ts +1 -1
- package/dist/lib/services/email_service.d.ts.map +1 -1
- package/dist/lib/services/email_service.js +2 -0
- package/dist/lib/services/email_template_manifest.d.ts.map +1 -1
- package/dist/lib/services/email_template_manifest.js +17 -0
- package/dist/lib/services/email_templates/otp_signin_code.html +13 -0
- package/dist/lib/services/email_templates/otp_signin_code.txt +5 -0
- package/dist/lib/services/index.d.ts +2 -0
- package/dist/lib/services/index.d.ts.map +1 -1
- package/dist/lib/services/index.js +1 -0
- package/dist/lib/services/otp_service.d.ts +46 -0
- package/dist/lib/services/otp_service.d.ts.map +1 -0
- package/dist/lib/services/otp_service.js +238 -0
- package/dist/lib/services/session_token_service.d.ts +3 -1
- package/dist/lib/services/session_token_service.d.ts.map +1 -1
- package/dist/lib/services/session_token_service.js +4 -2
- package/dist/page_components/otp.d.ts +4 -0
- package/dist/page_components/otp.d.ts.map +1 -0
- package/dist/page_components/otp.js +5 -0
- package/dist/server/routes/index.d.ts +2 -0
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +3 -0
- package/dist/server/routes/me.d.ts.map +1 -1
- package/dist/server/routes/me.js +43 -1
- package/dist/server/routes/otp/request.d.ts +3 -0
- package/dist/server/routes/otp/request.d.ts.map +1 -0
- package/dist/server/routes/otp/request.js +33 -0
- package/dist/server/routes/otp/verify.d.ts +3 -0
- package/dist/server/routes/otp/verify.d.ts.map +1 -0
- package/dist/server/routes/otp/verify.js +58 -0
- package/dist/server-lib.d.ts +3 -0
- package/dist/server-lib.d.ts.map +1 -1
- package/dist/server-lib.js +2 -0
- package/dist/server_pages/login.d.ts.map +1 -1
- package/dist/server_pages/login.js +1 -1
- package/dist/server_pages/login_client_wrapper.d.ts +1 -1
- package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/login_client_wrapper.js +2 -2
- package/dist/server_pages/otp.d.ts +42 -0
- package/dist/server_pages/otp.d.ts.map +1 -0
- package/dist/server_pages/otp.js +38 -0
- 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;
|
|
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"}
|
package/dist/cli/validate.js
CHANGED
|
@@ -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";
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAYA,cAAc,oBAAoB,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":"
|
|
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 {
|
|
8
|
-
import {
|
|
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"}
|