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
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`.**
|
package/SETUP_CHECKLIST.md
CHANGED
|
@@ -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.
|
package/cli-src/cli/validate.ts
CHANGED
|
@@ -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>
|
|
@@ -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";
|