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
package/README.md CHANGED
@@ -2,6 +2,76 @@
2
2
 
3
3
  A reusable authentication UI component package powered by Next.js, TailwindCSS, and shadcn. It integrates `hazo_config` for configuration management and `hazo_connect` for data access, enabling future components to stay aligned with platform conventions.
4
4
 
5
+ ## What's New in v6.1.0
6
+
7
+ **Email-OTP sign-in** — a passwordless, OAuth-free way to sign in via a 6-digit code emailed to the user. Targeted at single-operator deployments that don't want to wire Google OAuth or manage passwords.
8
+
9
+ ### Highlights
10
+
11
+ - **New routes** — `POST /api/hazo_auth/otp/request` and `POST /api/hazo_auth/otp/verify`
12
+ - **New components** — `<OTPRequestForm/>` and `<OTPVerifyForm/>` exported from `hazo_auth/client`
13
+ - **New page** — `/hazo_auth/otp` (zero-config) via `hazo_auth/pages/otp`
14
+ - **Sliding 7-day session** — OTP sessions auto-extend on `/me` when within 24h of expiry. OAuth/password sessions keep their existing 30-day fixed behaviour.
15
+ - **Auto-register (opt-in)** — set `otp_auto_register = true` to let unknown emails sign in on first successful /verify
16
+ - **Rate limited** — 3 requests/email/15min, 20 requests/IP/hour; per-code attempts capped at 5
17
+
18
+ ### Consumer example
19
+
20
+ Mount the routes:
21
+
22
+ ```ts
23
+ // app/api/hazo_auth/otp/request/route.ts
24
+ export { otpRequestPOST as POST } from "hazo_auth/server/routes";
25
+
26
+ // app/api/hazo_auth/otp/verify/route.ts
27
+ export { otpVerifyPOST as POST } from "hazo_auth/server/routes";
28
+ ```
29
+
30
+ Render the zero-config page:
31
+
32
+ ```tsx
33
+ // app/hazo_auth/otp/page.tsx
34
+ export { default } from "hazo_auth/pages/otp";
35
+ ```
36
+
37
+ Or compose components yourself:
38
+
39
+ ```tsx
40
+ "use client";
41
+ import { OTPRequestForm, OTPVerifyForm } from "hazo_auth/client";
42
+ import { useState } from "react";
43
+
44
+ export default function CustomOtp() {
45
+ const [sent_to, set_sent_to] = useState<string | null>(null);
46
+ return sent_to
47
+ ? <OTPVerifyForm email={sent_to} redirect_url="/" />
48
+ : <OTPRequestForm on_sent={set_sent_to} />;
49
+ }
50
+ ```
51
+
52
+ ### Required setup
53
+
54
+ 1. Apply migration: `npm run migrate migrations/015_email_otp.sql`
55
+ 2. Add `[hazo_auth__otp]` section to `config/hazo_auth_config.ini` (template in `hazo_auth_config.example.ini`)
56
+ 3. `hazo_notify ^3.0.0` (existing peer dep) must be configured to deliver email
57
+ 4. New peer dep: `input-otp` (optional; required only if you render `<OTPVerifyForm/>`)
58
+ 5. **Add OTP routes to your middleware/proxy `public_routes`** — unauthenticated users land on the OTP page, so the page route and its API routes must bypass auth guards:
59
+
60
+ ```typescript
61
+ // middleware.ts / proxy.ts (in your consuming app)
62
+ const public_routes = [
63
+ // ... existing entries ...
64
+ "/hazo_auth/otp", // OTP sign-in page (public — users arrive unauthenticated)
65
+ "/api/hazo_auth/otp", // OTP request + verify API routes
66
+ ];
67
+ ```
68
+
69
+ Without this your middleware redirects unauthenticated users away from the sign-in page before they can authenticate.
70
+
71
+ See `MIGRATION.md` "v6.0.x → v6.1" for the full upgrade walkthrough.
72
+
73
+ ---
74
+
5
75
  ### What's New in v6.0.0 🚨 BREAKING CHANGE
6
76
 
7
77
  **`TenantAuthResult.organization` / `.organization_id` renamed to `.selected_scope` / `.selected_scope_id`.**
@@ -1443,6 +1443,98 @@ export { POST } from "hazo_auth/server/routes/pin_login";
1443
1443
 
1444
1444
  ---
1445
1445
 
1446
+ ## Phase 5.3: Email-OTP Sign-in Setup (Optional)
1447
+
1448
+ Email-OTP sign-in lets users authenticate with a 6-digit code sent to their email — no password or Google OAuth required. Skip this phase if your app uses only password or OAuth authentication.
1449
+
1450
+ ### Step 5.3.1: Apply the OTP migration
1451
+
1452
+ ```bash
1453
+ npm run migrate migrations/015_email_otp.sql
1454
+ ```
1455
+
1456
+ This creates the `hazo_email_otps` table (stores hashed codes with expiry).
1457
+
1458
+ **Verify the table exists:**
1459
+ ```bash
1460
+ sqlite3 data/hazo_auth.sqlite ".tables" | tr ' ' '\n' | grep hazo_email_otps
1461
+ # Expected: hazo_email_otps
1462
+ ```
1463
+
1464
+ ### Step 5.3.2: Add OTP config section
1465
+
1466
+ Add to `config/hazo_auth_config.ini`:
1467
+
1468
+ ```ini
1469
+ [hazo_auth__otp]
1470
+ # Whether OTP sign-in is enabled
1471
+ enabled = true
1472
+
1473
+ # Auto-register unknown emails on first successful verify (default: false)
1474
+ otp_auto_register = false
1475
+
1476
+ # Code expiry in minutes (default: 10)
1477
+ code_expiry_minutes = 10
1478
+ ```
1479
+
1480
+ ### Step 5.3.3: Create OTP API routes
1481
+
1482
+ ```bash
1483
+ npx hazo_auth generate-routes
1484
+ ```
1485
+
1486
+ Or manually:
1487
+
1488
+ `app/api/hazo_auth/otp/request/route.ts`:
1489
+ ```typescript
1490
+ export { otpRequestPOST as POST } from "hazo_auth/server/routes";
1491
+ ```
1492
+
1493
+ `app/api/hazo_auth/otp/verify/route.ts`:
1494
+ ```typescript
1495
+ export { otpVerifyPOST as POST } from "hazo_auth/server/routes";
1496
+ ```
1497
+
1498
+ ### Step 5.3.4: Create the OTP sign-in page
1499
+
1500
+ `app/hazo_auth/otp/page.tsx`:
1501
+ ```typescript
1502
+ export { default } from "hazo_auth/pages/otp";
1503
+ ```
1504
+
1505
+ ### Step 5.3.5: Add OTP routes to middleware/proxy public_routes (REQUIRED)
1506
+
1507
+ **This step is critical.** Unauthenticated users arrive at the OTP sign-in page before they have auth cookies. If the OTP routes are not in `public_routes`, your middleware will redirect them to `/login` in a loop.
1508
+
1509
+ Edit your `middleware.ts` or `proxy.ts`:
1510
+
1511
+ ```typescript
1512
+ const public_routes = [
1513
+ // ... existing entries ...
1514
+ "/hazo_auth/otp", // OTP sign-in page (public — users arrive unauthenticated)
1515
+ "/api/hazo_auth/otp", // OTP request + verify API routes
1516
+ ];
1517
+ ```
1518
+
1519
+ ### Step 5.3.6: Install optional peer dep (if using OTPVerifyForm)
1520
+
1521
+ If you render `<OTPVerifyForm/>` directly (rather than using the zero-config page), install:
1522
+
1523
+ ```bash
1524
+ npm install input-otp
1525
+ ```
1526
+
1527
+ **OTP Setup Checklist:**
1528
+ - [ ] `hazo_email_otps` table created (`npm run migrate migrations/015_email_otp.sql`)
1529
+ - [ ] `[hazo_auth__otp]` section added to `hazo_auth_config.ini`
1530
+ - [ ] OTP API routes created (`/api/hazo_auth/otp/request` and `/api/hazo_auth/otp/verify`)
1531
+ - [ ] OTP page created (`/hazo_auth/otp`)
1532
+ - [ ] `/hazo_auth/otp` and `/api/hazo_auth/otp` added to middleware `public_routes`
1533
+ - [ ] `input-otp` installed (only if using `<OTPVerifyForm/>` directly)
1534
+ - [ ] Email delivery configured (`hazo_notify` + valid `ZEPTOMAIL_API_KEY`)
1535
+
1536
+ ---
1537
+
1446
1538
  ## Phase 6: Verification Tests
1447
1539
 
1448
1540
  Run these tests to verify your setup is working correctly.
@@ -64,6 +64,9 @@ const REQUIRED_API_ROUTES = [
64
64
  { path: "api/hazo_auth/user_management/users/roles", method: "GET" },
65
65
  { path: "api/hazo_auth/user_management/users/roles", method: "POST" },
66
66
  { path: "api/hazo_auth/user_management/users/roles", method: "PUT" },
67
+ // OTP routes
68
+ { path: "api/hazo_auth/otp/request", method: "POST" },
69
+ { path: "api/hazo_auth/otp/verify", method: "POST" },
67
70
  ];
68
71
 
69
72
  // section: helpers
@@ -534,6 +537,7 @@ const REQUIRED_TABLES = [
534
537
  "hazo_role_permissions",
535
538
  "hazo_invitations",
536
539
  "hazo_refresh_tokens",
540
+ "hazo_email_otps",
537
541
  ];
538
542
 
539
543
  const TEXT_ID_TABLES = [
@@ -27,6 +27,7 @@ export const BASE_COOKIE_NAMES = {
27
27
  USER_ID: "hazo_auth_user_id",
28
28
  USER_EMAIL: "hazo_auth_user_email",
29
29
  SESSION: "hazo_auth_session",
30
+ SESSION_KIND: "hazo_auth_session_kind", // v6.1: marks OTP-issued sessions so /me can apply sliding expiry
30
31
  DEV_LOCK: "hazo_auth_dev_lock",
31
32
  SCOPE_ID: "hazo_auth_scope_id", // v5.2: Tenant context cookie for multi-tenancy
32
33
  ANON_ID: "hazo_auth_anon_id", // v5.2: Stable opaque per-visitor ID for anonymous flows (e.g. hazo_feedback)
@@ -31,6 +31,12 @@ export type LoginConfig = {
31
31
  imageBackgroundColor: string;
32
32
  /** OAuth configuration */
33
33
  oauth: OAuthConfig;
34
+ /** Whether the OTP sign-in link is shown below the login form */
35
+ otpSigninEnabled: boolean;
36
+ /** Label for the OTP sign-in link */
37
+ otpSigninLabel: string;
38
+ /** href for the OTP sign-in link */
39
+ otpSigninHref: string;
34
40
  };
35
41
 
36
42
  // section: helpers
@@ -94,6 +100,11 @@ export function get_login_config(): LoginConfig {
94
100
  // Get OAuth configuration
95
101
  const oauth = get_oauth_config();
96
102
 
103
+ // OTP sign-in link
104
+ const otpSigninEnabled = get_config_value(section, "otp_signin_enabled", "false") === "true";
105
+ const otpSigninLabel = get_config_value(section, "otp_signin_label", "Sign in with email code");
106
+ const otpSigninHref = get_config_value(section, "otp_signin_href", "/hazo_auth/otp");
107
+
97
108
  return {
98
109
  redirectRoute,
99
110
  successMessage,
@@ -111,6 +122,9 @@ export function get_login_config(): LoginConfig {
111
122
  imageAlt,
112
123
  imageBackgroundColor,
113
124
  oauth,
125
+ otpSigninEnabled,
126
+ otpSigninLabel,
127
+ otpSigninHref,
114
128
  };
115
129
  }
116
130
 
@@ -0,0 +1,91 @@
1
+ // file_description: server-only helper to read OTP sign-in configuration from hazo_auth_config.ini
2
+ // section: server-only-guard
3
+ import "server-only";
4
+
5
+ // section: imports
6
+ import { get_config_value, get_config_boolean, get_config_number } from "./config/config_loader.server.js";
7
+
8
+ // section: defaults
9
+ export const OTP_CONFIG_DEFAULTS = {
10
+ auto_register: false,
11
+ code_ttl_seconds: 600,
12
+ session_ttl_seconds: 604800,
13
+ slide_when_within_seconds: 86400,
14
+ email_rate_limit_max: 3,
15
+ email_rate_limit_window_seconds: 900,
16
+ ip_rate_limit_max: 20,
17
+ ip_rate_limit_window_seconds: 3600,
18
+ max_verify_attempts: 5,
19
+ auto_assign_scope_id: "00000000-0000-0000-0000-000000000001",
20
+ auto_assign_role_name: "member",
21
+ } as const;
22
+
23
+ // section: types
24
+ export type OtpConfig = {
25
+ /** Whether to automatically register a new user when an unrecognised email requests an OTP */
26
+ auto_register: boolean;
27
+ /** How long (seconds) a generated OTP code is valid */
28
+ code_ttl_seconds: number;
29
+ /** How long (seconds) the session created after successful OTP verification lasts */
30
+ session_ttl_seconds: number;
31
+ /** Slide the session expiry when the remaining TTL falls below this many seconds */
32
+ slide_when_within_seconds: number;
33
+ /** Maximum OTP requests allowed per email address within the rate-limit window */
34
+ email_rate_limit_max: number;
35
+ /** Rate-limit window (seconds) for per-email OTP requests */
36
+ email_rate_limit_window_seconds: number;
37
+ /** Maximum OTP requests allowed per IP address within the rate-limit window */
38
+ ip_rate_limit_max: number;
39
+ /** Rate-limit window (seconds) for per-IP OTP requests */
40
+ ip_rate_limit_window_seconds: number;
41
+ /** Maximum failed verify attempts before the OTP code is invalidated */
42
+ max_verify_attempts: number;
43
+ /** Scope ID to auto-assign a newly registered OTP user to */
44
+ auto_assign_scope_id: string;
45
+ /** Role name to assign within the auto-assign scope */
46
+ auto_assign_role_name: string;
47
+ };
48
+
49
+ // section: helpers
50
+ /**
51
+ * Reads OTP configuration from hazo_auth_config.ini [hazo_auth__otp] section.
52
+ * Falls back to defaults if the config file or section is missing.
53
+ */
54
+ export function get_otp_config(): OtpConfig {
55
+ const section = "hazo_auth__otp";
56
+ const d = OTP_CONFIG_DEFAULTS;
57
+
58
+ return {
59
+ auto_register: get_config_boolean(section, "otp_auto_register", d.auto_register),
60
+ code_ttl_seconds: get_config_number(section, "otp_code_ttl_seconds", d.code_ttl_seconds),
61
+ session_ttl_seconds: get_config_number(section, "otp_session_ttl_seconds", d.session_ttl_seconds),
62
+ slide_when_within_seconds: get_config_number(
63
+ section,
64
+ "otp_slide_when_within_seconds",
65
+ d.slide_when_within_seconds
66
+ ),
67
+ email_rate_limit_max: get_config_number(section, "otp_email_rate_limit_max", d.email_rate_limit_max),
68
+ email_rate_limit_window_seconds: get_config_number(
69
+ section,
70
+ "otp_email_rate_limit_window_seconds",
71
+ d.email_rate_limit_window_seconds
72
+ ),
73
+ ip_rate_limit_max: get_config_number(section, "otp_ip_rate_limit_max", d.ip_rate_limit_max),
74
+ ip_rate_limit_window_seconds: get_config_number(
75
+ section,
76
+ "otp_ip_rate_limit_window_seconds",
77
+ d.ip_rate_limit_window_seconds
78
+ ),
79
+ max_verify_attempts: get_config_number(section, "otp_max_verify_attempts", d.max_verify_attempts),
80
+ auto_assign_scope_id: get_config_value(section, "otp_auto_assign_scope_id", d.auto_assign_scope_id),
81
+ auto_assign_role_name: get_config_value(section, "otp_auto_assign_role_name", d.auto_assign_role_name),
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Convenience accessor — returns just the session TTL seconds from OTP config.
87
+ * Suitable for passing to token-creation utilities.
88
+ */
89
+ export function hazo_auth_otp_session_ttl_seconds(): number {
90
+ return get_otp_config().session_ttl_seconds;
91
+ }
@@ -14,7 +14,7 @@ export type EmailOptions = {
14
14
  text_body?: string;
15
15
  };
16
16
 
17
- export type EmailTemplateType = "forgot_password" | "email_verification" | "password_changed";
17
+ export type EmailTemplateType = "forgot_password" | "email_verification" | "password_changed" | "otp_signin_code";
18
18
 
19
19
  export type EmailTemplateData = {
20
20
  token?: string;
@@ -243,6 +243,8 @@ function get_email_subject(template_type: EmailTemplateType): string {
243
243
  return "Reset Your Password";
244
244
  case "password_changed":
245
245
  return "Password Changed Successfully";
246
+ case "otp_signin_code":
247
+ return "Your sign-in code";
246
248
  default:
247
249
  return "Email from hazo_auth";
248
250
  }
@@ -101,4 +101,21 @@ export const hazo_auth_template_manifest: SystemTemplateManifest[] = [
101
101
  },
102
102
  ],
103
103
  },
104
+ {
105
+ template_name: "otp_signin_code",
106
+ template_label: "OTP sign-in code",
107
+ category: SYSTEM_CATEGORY,
108
+ html: read_template("otp_signin_code", "html"),
109
+ text: read_template("otp_signin_code", "txt"),
110
+ variables: [
111
+ {
112
+ variable_name: "otp_code",
113
+ variable_description: "6-digit OTP code for email sign-in (v6.1.0+)",
114
+ },
115
+ {
116
+ variable_name: "expires_in_minutes",
117
+ variable_description: "Number of minutes until the OTP code expires",
118
+ },
119
+ ],
120
+ },
104
121
  ];
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Your sign-in code</title>
6
+ </head>
7
+ <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
8
+ <p>Your sign-in code is:</p>
9
+ <p style="font-size: 28px; font-weight: bold; letter-spacing: 0.2em; font-family: monospace; margin: 16px 0;">{{otp_code}}</p>
10
+ <p>This code expires in {{expires_in_minutes}} minutes.</p>
11
+ <p style="color: #666; font-size: 12px;">If you didn't request this code, you can safely ignore this email.</p>
12
+ </body>
13
+ </html>
@@ -0,0 +1,5 @@
1
+ Your sign-in code is: {{otp_code}}
2
+
3
+ This code expires in {{expires_in_minutes}} minutes.
4
+
5
+ If you didn't request this code, you can safely ignore this email.
@@ -20,5 +20,11 @@ export * from "./scope_service.js";
20
20
  export * from "./user_scope_service.js";
21
21
  export * from "./oauth_service.js";
22
22
  export * from "./branding_service.js";
23
-
24
-
23
+ export {
24
+ request_email_otp,
25
+ verify_email_otp,
26
+ generate_otp_code,
27
+ hash_otp_code,
28
+ verify_otp_code,
29
+ } from "./otp_service.js";
30
+ export type { RequestEmailOTPResult, VerifyEmailOTPResult } from "./otp_service";