hazo_auth 7.0.1 → 7.0.2
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 +73 -330
- package/SETUP_CHECKLIST.md +28 -248
- package/cli-src/cli/generate.ts +1 -10
- package/cli-src/cli/validate.ts +0 -4
- package/cli-src/lib/auth/auth_types.ts +12 -21
- package/cli-src/lib/auth/hazo_get_tenant_auth.server.ts +24 -25
- package/cli-src/lib/auth/index.ts +2 -2
- package/cli-src/lib/auth/nextauth_config.ts +27 -67
- package/cli-src/lib/auth/with_auth.server.ts +15 -15
- package/cli-src/lib/config/default_config.ts +8 -0
- package/cli-src/lib/cookies_config.server.ts +1 -1
- package/cli-src/lib/email_verification_config.server.ts +34 -0
- package/cli-src/lib/forgot_password_config.server.ts +34 -0
- package/cli-src/lib/login_config.server.ts +29 -14
- package/cli-src/lib/my_settings_config.server.ts +3 -0
- package/cli-src/lib/oauth_config.server.ts +31 -57
- package/cli-src/lib/register_config.server.ts +35 -11
- package/cli-src/lib/reset_password_config.server.ts +31 -0
- package/cli-src/lib/services/email_template_manifest.ts +0 -17
- package/cli-src/lib/services/index.ts +2 -8
- package/cli-src/lib/services/oauth_service.ts +74 -128
- package/cli-src/lib/services/otp_service.ts +7 -2
- package/cli-src/lib/services/session_token_service.ts +0 -2
- package/config/hazo_auth_config.example.ini +41 -76
- package/dist/cli/generate.d.ts.map +1 -1
- package/dist/cli/generate.js +1 -10
- package/dist/cli/validate.d.ts.map +1 -1
- package/dist/cli/validate.js +0 -4
- package/dist/client.d.ts +0 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +0 -1
- package/dist/components/layouts/create_firm/index.d.ts +8 -4
- package/dist/components/layouts/create_firm/index.d.ts.map +1 -1
- package/dist/components/layouts/create_firm/index.js +3 -3
- package/dist/components/layouts/email_verification/index.d.ts +5 -4
- package/dist/components/layouts/email_verification/index.d.ts.map +1 -1
- package/dist/components/layouts/email_verification/index.js +4 -4
- package/dist/components/layouts/forgot_password/index.d.ts +5 -4
- package/dist/components/layouts/forgot_password/index.d.ts.map +1 -1
- package/dist/components/layouts/forgot_password/index.js +2 -2
- package/dist/components/layouts/login/index.d.ts +13 -19
- package/dist/components/layouts/login/index.d.ts.map +1 -1
- package/dist/components/layouts/login/index.js +8 -11
- package/dist/components/layouts/otp/index.d.ts +5 -1
- package/dist/components/layouts/otp/index.d.ts.map +1 -1
- package/dist/components/layouts/otp/index.js +2 -2
- package/dist/components/layouts/register/index.d.ts +11 -11
- package/dist/components/layouts/register/index.d.ts.map +1 -1
- package/dist/components/layouts/register/index.js +6 -7
- package/dist/components/layouts/reset_password/index.d.ts +5 -4
- package/dist/components/layouts/reset_password/index.d.ts.map +1 -1
- package/dist/components/layouts/reset_password/index.js +5 -5
- package/dist/components/layouts/shared/components/already_logged_in_guard.d.ts +5 -3
- package/dist/components/layouts/shared/components/already_logged_in_guard.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/already_logged_in_guard.js +2 -2
- package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts +2 -6
- package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/facebook_sign_in_button.js +11 -13
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +3 -8
- package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts +6 -3
- package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/two_column_auth_layout.js +5 -8
- package/dist/components/layouts/shared/index.d.ts +2 -0
- package/dist/components/layouts/shared/index.d.ts.map +1 -1
- package/dist/components/layouts/shared/index.js +1 -0
- package/dist/components/layouts/user_management/index.d.ts.map +1 -1
- package/dist/components/layouts/user_management/index.js +39 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/auth/auth_types.d.ts +12 -13
- package/dist/lib/auth/auth_types.d.ts.map +1 -1
- package/dist/lib/auth/auth_types.js +0 -8
- package/dist/lib/auth/hazo_get_tenant_auth.server.d.ts +7 -8
- package/dist/lib/auth/hazo_get_tenant_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_tenant_auth.server.js +22 -23
- package/dist/lib/auth/index.d.ts +2 -2
- package/dist/lib/auth/index.d.ts.map +1 -1
- package/dist/lib/auth/nextauth_config.d.ts +0 -10
- package/dist/lib/auth/nextauth_config.d.ts.map +1 -1
- package/dist/lib/auth/nextauth_config.js +23 -52
- package/dist/lib/auth/with_auth.server.d.ts +13 -13
- package/dist/lib/auth/with_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/with_auth.server.js +2 -2
- package/dist/lib/config/default_config.d.ts +16 -0
- package/dist/lib/config/default_config.d.ts.map +1 -1
- package/dist/lib/config/default_config.js +8 -0
- package/dist/lib/cookies_config.server.d.ts +1 -1
- package/dist/lib/cookies_config.server.js +1 -1
- package/dist/lib/email_verification_config.server.d.ts +3 -0
- package/dist/lib/email_verification_config.server.d.ts.map +1 -1
- package/dist/lib/email_verification_config.server.js +15 -0
- package/dist/lib/forgot_password_config.server.d.ts +3 -0
- package/dist/lib/forgot_password_config.server.d.ts.map +1 -1
- package/dist/lib/forgot_password_config.server.js +15 -0
- package/dist/lib/login_config.server.d.ts +3 -6
- package/dist/lib/login_config.server.d.ts.map +1 -1
- package/dist/lib/login_config.server.js +11 -7
- package/dist/lib/my_settings_config.server.d.ts +1 -0
- package/dist/lib/my_settings_config.server.d.ts.map +1 -1
- package/dist/lib/my_settings_config.server.js +2 -0
- package/dist/lib/oauth_config.server.d.ts +8 -17
- package/dist/lib/oauth_config.server.d.ts.map +1 -1
- package/dist/lib/oauth_config.server.js +10 -25
- package/dist/lib/register_config.server.d.ts +5 -2
- package/dist/lib/register_config.server.d.ts.map +1 -1
- package/dist/lib/register_config.server.js +15 -4
- package/dist/lib/reset_password_config.server.d.ts +3 -0
- package/dist/lib/reset_password_config.server.d.ts.map +1 -1
- package/dist/lib/reset_password_config.server.js +13 -0
- package/dist/lib/services/email_template_manifest.d.ts.map +1 -1
- package/dist/lib/services/email_template_manifest.js +0 -17
- package/dist/lib/services/index.d.ts +0 -2
- package/dist/lib/services/index.d.ts.map +1 -1
- package/dist/lib/services/index.js +0 -1
- package/dist/lib/services/oauth_service.d.ts +11 -22
- package/dist/lib/services/oauth_service.d.ts.map +1 -1
- package/dist/lib/services/oauth_service.js +63 -96
- package/dist/lib/services/otp_service.d.ts +1 -1
- package/dist/lib/services/otp_service.d.ts.map +1 -1
- package/dist/lib/services/otp_service.js +6 -1
- package/dist/lib/services/session_token_service.d.ts +0 -2
- package/dist/lib/services/session_token_service.d.ts.map +1 -1
- package/dist/lib/services/session_token_service.js +0 -2
- package/dist/page_components/create_firm.d.ts +1 -13
- package/dist/page_components/create_firm.d.ts.map +1 -1
- package/dist/page_components/create_firm.js +6 -10
- package/dist/page_components/forgot_password.d.ts +4 -1
- package/dist/page_components/forgot_password.d.ts.map +1 -1
- package/dist/page_components/forgot_password.js +6 -2
- package/dist/page_components/login.d.ts +4 -1
- package/dist/page_components/login.d.ts.map +1 -1
- package/dist/page_components/login.js +6 -2
- package/dist/page_components/register.d.ts +4 -1
- package/dist/page_components/register.d.ts.map +1 -1
- package/dist/page_components/register.js +6 -2
- package/dist/page_components/reset_password.d.ts +4 -1
- package/dist/page_components/reset_password.d.ts.map +1 -1
- package/dist/page_components/reset_password.js +6 -2
- package/dist/page_components/verify_email.d.ts +4 -1
- package/dist/page_components/verify_email.d.ts.map +1 -1
- package/dist/page_components/verify_email.js +6 -2
- package/dist/server/routes/assets.d.ts +8 -0
- package/dist/server/routes/assets.d.ts.map +1 -0
- package/dist/server/routes/assets.js +38 -0
- package/dist/server/routes/consent_me.d.ts +4 -0
- package/dist/server/routes/consent_me.d.ts.map +1 -0
- package/dist/server/routes/consent_me.js +15 -0
- package/dist/server/routes/index.d.ts +6 -4
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +9 -5
- package/dist/server/routes/me.d.ts.map +1 -1
- package/dist/server/routes/me.js +1 -43
- package/dist/server/routes/oauth_facebook_callback.d.ts +1 -1
- package/dist/server/routes/oauth_facebook_callback.d.ts.map +1 -1
- package/dist/server/routes/oauth_facebook_callback.js +8 -1
- package/dist/server/routes/oauth_google_callback.js +1 -1
- package/dist/server/routes/otp/verify.js +2 -2
- package/dist/server/routes/strings_defaults.d.ts +4 -0
- package/dist/server/routes/strings_defaults.d.ts.map +1 -0
- package/dist/server/routes/strings_defaults.js +7 -0
- package/dist/server/routes/user_management_users.d.ts +11 -0
- package/dist/server/routes/user_management_users.d.ts.map +1 -1
- package/dist/server/routes/user_management_users.js +50 -0
- package/dist/server-lib.d.ts +0 -3
- package/dist/server-lib.d.ts.map +1 -1
- package/dist/server-lib.js +0 -2
- package/dist/server_pages/forgot_password.d.ts +18 -14
- package/dist/server_pages/forgot_password.d.ts.map +1 -1
- package/dist/server_pages/forgot_password.js +14 -12
- package/dist/server_pages/forgot_password_client_wrapper.d.ts +8 -7
- package/dist/server_pages/forgot_password_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/forgot_password_client_wrapper.js +2 -2
- package/dist/server_pages/index.d.ts +2 -0
- package/dist/server_pages/index.d.ts.map +1 -1
- package/dist/server_pages/index.js +1 -0
- package/dist/server_pages/login.d.ts +22 -23
- package/dist/server_pages/login.d.ts.map +1 -1
- package/dist/server_pages/login.js +27 -14
- package/dist/server_pages/login_client_wrapper.d.ts +9 -10
- 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/my_settings.d.ts +1 -3
- package/dist/server_pages/my_settings.d.ts.map +1 -1
- package/dist/server_pages/my_settings.js +2 -9
- package/dist/server_pages/register.d.ts +17 -20
- package/dist/server_pages/register.d.ts.map +1 -1
- package/dist/server_pages/register.js +20 -15
- package/dist/server_pages/register_client_wrapper.d.ts +8 -10
- package/dist/server_pages/register_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/register_client_wrapper.js +2 -2
- package/dist/server_pages/reset_password.d.ts +16 -11
- package/dist/server_pages/reset_password.d.ts.map +1 -1
- package/dist/server_pages/reset_password.js +14 -10
- package/dist/server_pages/reset_password_client_wrapper.d.ts +8 -7
- package/dist/server_pages/reset_password_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/reset_password_client_wrapper.js +2 -2
- package/dist/server_pages/verify_email.d.ts +18 -12
- package/dist/server_pages/verify_email.d.ts.map +1 -1
- package/dist/server_pages/verify_email.js +13 -11
- package/dist/server_pages/verify_email_client_wrapper.d.ts +8 -7
- package/dist/server_pages/verify_email_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/verify_email_client_wrapper.js +2 -2
- package/dist/themes/index.d.ts +0 -1
- package/dist/themes/index.d.ts.map +1 -1
- package/dist/themes/index.js +1 -1
- package/package.json +26 -40
- package/dist/themes/preset_indigo_sunset.d.ts +0 -3
- package/dist/themes/preset_indigo_sunset.d.ts.map +0 -1
- package/dist/themes/preset_indigo_sunset.js +0 -20
package/README.md
CHANGED
|
@@ -2,176 +2,6 @@
|
|
|
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 v7.0.0
|
|
6
|
-
|
|
7
|
-
**Themes, cookie consent, text overrides, and Facebook OAuth.**
|
|
8
|
-
|
|
9
|
-
### Highlights
|
|
10
|
-
|
|
11
|
-
- **Pluggable theme system** — `createTheme` + `<HazoAuthThemeProvider>` (zero FOUC, compiles to shadcn CSS variables). Use a preset or build from scratch.
|
|
12
|
-
- **Facebook OAuth** — `<FacebookSignInButton>` and `handle_facebook_oauth_login` service. Strict linking by default (verified accounts only).
|
|
13
|
-
- **Cookie consent banner** — `<CookieConsentBanner>` from `hazo_auth/consent`. GTM optional, 4 categories, theme-driven.
|
|
14
|
-
- **Text override system** — `<HazoAuthStringsProvider>` and per-page `title`/`subtitle`/`ctaText`/`legalText` props. INI text keys removed.
|
|
15
|
-
- **Preset themes** — `preset_neutral` (default look, no config needed) and `preset_indigo_sunset` from `hazo_auth/themes`.
|
|
16
|
-
|
|
17
|
-
### Breaking Changes Summary
|
|
18
|
-
|
|
19
|
-
- Per-page `imageSrc`/`imageAlt`/`imageBackgroundColor` props removed — use `theme.brandPanel`.
|
|
20
|
-
- INI text keys (`title`, `subtitle`, `cta_text`, `legal_text`) removed from layout sections — use `HazoAuthStringsProvider` or per-page props.
|
|
21
|
-
- Facebook OAuth requires new env vars (`HAZO_AUTH_FACEBOOK_APP_ID`, `HAZO_AUTH_FACEBOOK_APP_SECRET`).
|
|
22
|
-
|
|
23
|
-
> Full upgrade guide: [MIGRATION.md](./MIGRATION.md)
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## What's New in v6.1.0
|
|
28
|
-
|
|
29
|
-
**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.
|
|
30
|
-
|
|
31
|
-
### Highlights
|
|
32
|
-
|
|
33
|
-
- **New routes** — `POST /api/hazo_auth/otp/request` and `POST /api/hazo_auth/otp/verify`
|
|
34
|
-
- **New components** — `<OTPRequestForm/>` and `<OTPVerifyForm/>` exported from `hazo_auth/client`
|
|
35
|
-
- **New page** — `/hazo_auth/otp` (zero-config) via `hazo_auth/pages/otp`
|
|
36
|
-
- **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.
|
|
37
|
-
- **Auto-register (opt-in)** — set `otp_auto_register = true` to let unknown emails sign in on first successful /verify
|
|
38
|
-
- **Rate limited** — 3 requests/email/15min, 20 requests/IP/hour; per-code attempts capped at 5
|
|
39
|
-
|
|
40
|
-
### Consumer example
|
|
41
|
-
|
|
42
|
-
Mount the routes:
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
// app/api/hazo_auth/otp/request/route.ts
|
|
46
|
-
export { otpRequestPOST as POST } from "hazo_auth/server/routes";
|
|
47
|
-
|
|
48
|
-
// app/api/hazo_auth/otp/verify/route.ts
|
|
49
|
-
export { otpVerifyPOST as POST } from "hazo_auth/server/routes";
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Render the zero-config page:
|
|
53
|
-
|
|
54
|
-
```tsx
|
|
55
|
-
// app/hazo_auth/otp/page.tsx
|
|
56
|
-
export { default } from "hazo_auth/pages/otp";
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Or compose components yourself:
|
|
60
|
-
|
|
61
|
-
```tsx
|
|
62
|
-
"use client";
|
|
63
|
-
import { OTPRequestForm, OTPVerifyForm } from "hazo_auth/client";
|
|
64
|
-
import { useState } from "react";
|
|
65
|
-
|
|
66
|
-
export default function CustomOtp() {
|
|
67
|
-
const [sent_to, set_sent_to] = useState<string | null>(null);
|
|
68
|
-
return sent_to
|
|
69
|
-
? <OTPVerifyForm email={sent_to} redirect_url="/" />
|
|
70
|
-
: <OTPRequestForm on_sent={set_sent_to} />;
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### Required setup
|
|
75
|
-
|
|
76
|
-
1. Apply migration: `npm run migrate migrations/015_email_otp.sql`
|
|
77
|
-
2. Add `[hazo_auth__otp]` section to `config/hazo_auth_config.ini` (template in `hazo_auth_config.example.ini`)
|
|
78
|
-
3. `hazo_notify ^3.0.0` (existing peer dep) must be configured to deliver email
|
|
79
|
-
4. New peer dep: `input-otp` (optional; required only if you render `<OTPVerifyForm/>`)
|
|
80
|
-
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:
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
// middleware.ts / proxy.ts (in your consuming app)
|
|
84
|
-
const public_routes = [
|
|
85
|
-
// ... existing entries ...
|
|
86
|
-
"/hazo_auth/otp", // OTP sign-in page (public — users arrive unauthenticated)
|
|
87
|
-
"/api/hazo_auth/otp", // OTP request + verify API routes
|
|
88
|
-
];
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
Without this your middleware redirects unauthenticated users away from the sign-in page before they can authenticate.
|
|
92
|
-
|
|
93
|
-
See `MIGRATION.md` "v6.0.x → v6.1" for the full upgrade walkthrough.
|
|
94
|
-
|
|
95
|
-
---
|
|
96
|
-
|
|
97
|
-
### What's New in v6.0.0 🚨 BREAKING CHANGE
|
|
98
|
-
|
|
99
|
-
**`TenantAuthResult.organization` / `.organization_id` renamed to `.selected_scope` / `.selected_scope_id`.**
|
|
100
|
-
|
|
101
|
-
The multi-tenancy model has been scope-based for several releases — `auth.user_scopes`, `auth.scope_access_via`, `auth.scope_ok`, the `hazo_auth_scope_id` cookie, the `X-Hazo-Scope-Id` header. The only holdouts using the legacy "organization" name were two fields on `TenantAuthResult` that were always just *"the currently selected scope"* under the hood. This release eliminates the inconsistency.
|
|
102
|
-
|
|
103
|
-
> 📖 **Full migration guide:** [MIGRATION.md → v5.x → v6.0](./MIGRATION.md#v5x--v60-organization--selected_scope)
|
|
104
|
-
|
|
105
|
-
**No behavioral change.** `selected_scope_id` is derived from the same scope-selection cookie/header that `organization_id` was. Wire-format auth responses change field names, but the underlying scope lookup is identical.
|
|
106
|
-
|
|
107
|
-
**No deprecation shim.** A clean find-replace is the upgrade path. If you absolutely need a transitional period, pin to `hazo_auth@^5.3` until you can do the rename in one shot.
|
|
108
|
-
|
|
109
|
-
**What to replace (find ↔ replace, all in your app code):**
|
|
110
|
-
|
|
111
|
-
| Find | Replace with |
|
|
112
|
-
| ------------------------------------------------- | -------------------------------------------------- |
|
|
113
|
-
| `auth.organization` | `auth.selected_scope` |
|
|
114
|
-
| `auth.organization_id` | `auth.selected_scope_id` |
|
|
115
|
-
| `import type { TenantOrganization }` | `import type { SelectedScope }` |
|
|
116
|
-
| `: TenantOrganization` | `: SelectedScope` |
|
|
117
|
-
| `AuthenticatedTenantAuthWithOrg` | `AuthenticatedTenantAuthWithSelectedScope` |
|
|
118
|
-
|
|
119
|
-
**Before / after example:**
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
// BEFORE (v5.x)
|
|
123
|
-
import { hazo_get_tenant_auth } from "hazo_auth/server-lib";
|
|
124
|
-
import type { TenantOrganization } from "hazo_auth";
|
|
125
|
-
|
|
126
|
-
export async function GET(request: NextRequest) {
|
|
127
|
-
const auth = await hazo_get_tenant_auth(request);
|
|
128
|
-
if (!auth.authenticated || !auth.organization) {
|
|
129
|
-
return NextResponse.json({ error: "no tenant" }, { status: 403 });
|
|
130
|
-
}
|
|
131
|
-
const data = await getData(auth.organization_id); // or auth.organization.id
|
|
132
|
-
return NextResponse.json({ org: auth.organization, data });
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// AFTER (v6.0)
|
|
136
|
-
import { hazo_get_tenant_auth } from "hazo_auth/server-lib";
|
|
137
|
-
import type { SelectedScope } from "hazo_auth";
|
|
138
|
-
|
|
139
|
-
export async function GET(request: NextRequest) {
|
|
140
|
-
const auth = await hazo_get_tenant_auth(request);
|
|
141
|
-
if (!auth.authenticated || !auth.selected_scope) {
|
|
142
|
-
return NextResponse.json({ error: "no tenant scope" }, { status: 403 });
|
|
143
|
-
}
|
|
144
|
-
const data = await getData(auth.selected_scope_id); // or auth.selected_scope.id
|
|
145
|
-
return NextResponse.json({ selected_scope: auth.selected_scope, data });
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
**What did NOT change** (same names, same behavior, same wire format):
|
|
150
|
-
- `auth.user_scopes` — array of all scopes the user has access to
|
|
151
|
-
- `auth.scope_ok`, `auth.scope_access_via` — scope-access fields
|
|
152
|
-
- `hazo_auth_scope_id` cookie name and `X-Hazo-Scope-Id` request header
|
|
153
|
-
- All `Tenant*` type and class names: `TenantAuthResult`, `TenantAuthOptions`, `RequiredTenantAuthResult`, `TenantRequiredError`, `TenantAccessDeniedError`, `hazo_get_tenant_auth`, `require_tenant_auth`, `withAuth`'s `require_tenant` option
|
|
154
|
-
- The error response `{ code: "TENANT_REQUIRED" }` shape — only the human-readable `error` string was rephrased ("Organization context required" → "Tenant scope context required")
|
|
155
|
-
|
|
156
|
-
**One-liner upgrade for typical consumers:**
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
# Run from your app repo (NOT inside hazo_auth itself).
|
|
160
|
-
# Update the path glob to match your code layout.
|
|
161
|
-
git grep -l "organization\b\|TenantOrganization\|AuthenticatedTenantAuthWithOrg" -- 'src/**' 'app/**' 'lib/**' \
|
|
162
|
-
| xargs sed -i.bak '
|
|
163
|
-
s/auth\.organization_id\b/auth.selected_scope_id/g;
|
|
164
|
-
s/auth\.organization\b/auth.selected_scope/g;
|
|
165
|
-
s/\bTenantOrganization\b/SelectedScope/g;
|
|
166
|
-
s/\bAuthenticatedTenantAuthWithOrg\b/AuthenticatedTenantAuthWithSelectedScope/g;
|
|
167
|
-
'
|
|
168
|
-
# Then: review the diff carefully — sed is blunt. Remove .bak files when satisfied.
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
> ⚠️ The sed line is a starting point, not a blanket "trust me." It only catches `auth.organization*` and the type names. If you destructure (`const { organization } = auth`), aliased imports (`TenantOrganization as Org`), or reference `organization` for unrelated reasons (HTML autocomplete attribute, your own variables), the script either misses or wrongly rewrites them. **Always diff before committing.**
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
5
|
### What's New in v5.3.1 🔧
|
|
176
6
|
|
|
177
7
|
**`get_client_ip(request)` exported from `hazo_auth/server-lib`** — extracts the client IP from `x-forwarded-for` (first element), falling back to `x-real-ip`, then `"unknown"`. Previously private to `hazo_get_auth.server.ts`. Useful for consumers that need consistent IP extraction across handlers (e.g., `hazo_feedback` audit logging).
|
|
@@ -472,30 +302,23 @@ export default function Page() {
|
|
|
472
302
|
}
|
|
473
303
|
```
|
|
474
304
|
|
|
475
|
-
**Customizing Visual Appearance (
|
|
476
|
-
|
|
477
|
-
Use `HazoAuthThemeProvider` instead of per-page image props (which were removed in v7.0):
|
|
305
|
+
**Customizing Visual Appearance (Optional):**
|
|
478
306
|
|
|
479
|
-
```
|
|
480
|
-
|
|
307
|
+
```typescript
|
|
308
|
+
// All pages accept optional visual props
|
|
481
309
|
import { LoginPage } from "hazo_auth/pages/login";
|
|
482
310
|
|
|
483
|
-
const theme = createTheme({
|
|
484
|
-
layout: "split",
|
|
485
|
-
brandPanel: { logoSrc: "/logo.svg", tagline: "Welcome.", backgroundGradient: "linear-gradient(135deg, #3730A3, #F59E0B)" },
|
|
486
|
-
});
|
|
487
|
-
|
|
488
311
|
export default function Page() {
|
|
489
312
|
return (
|
|
490
|
-
<
|
|
491
|
-
|
|
492
|
-
|
|
313
|
+
<LoginPage
|
|
314
|
+
image_src="/custom-login-image.jpg"
|
|
315
|
+
image_alt="My company logo"
|
|
316
|
+
image_background_color="#f0f0f0"
|
|
317
|
+
/>
|
|
493
318
|
);
|
|
494
319
|
}
|
|
495
320
|
```
|
|
496
321
|
|
|
497
|
-
See the [Theming](#theming-v700) section for all options.
|
|
498
|
-
|
|
499
322
|
**Embedding MySettings in Your Dashboard:**
|
|
500
323
|
|
|
501
324
|
```typescript
|
|
@@ -685,132 +508,6 @@ The dark class is typically added by next-themes or similar theme providers.
|
|
|
685
508
|
|
|
686
509
|
---
|
|
687
510
|
|
|
688
|
-
## Theming (v7.0.0+)
|
|
689
|
-
|
|
690
|
-
hazo_auth v7 ships a pluggable theme system. `<HazoAuthThemeProvider>` is a Server Component that injects CSS variables at `:root` with zero FOUC.
|
|
691
|
-
|
|
692
|
-
### Option 1: Use a preset directly
|
|
693
|
-
|
|
694
|
-
```tsx
|
|
695
|
-
import { HazoAuthThemeProvider } from "hazo_auth/theme";
|
|
696
|
-
import { preset_indigo_sunset } from "hazo_auth/themes";
|
|
697
|
-
|
|
698
|
-
// app/layout.tsx
|
|
699
|
-
export default function RootLayout({ children }) {
|
|
700
|
-
return (
|
|
701
|
-
<html>
|
|
702
|
-
<body>
|
|
703
|
-
<HazoAuthThemeProvider theme={preset_indigo_sunset}>
|
|
704
|
-
{children}
|
|
705
|
-
</HazoAuthThemeProvider>
|
|
706
|
-
</body>
|
|
707
|
-
</html>
|
|
708
|
-
);
|
|
709
|
-
}
|
|
710
|
-
```
|
|
711
|
-
|
|
712
|
-
`preset_neutral` is the default look (slate-700 palette, centered layout). Auth pages render acceptably with no theme configuration at all.
|
|
713
|
-
|
|
714
|
-
### Option 2: Customize a preset
|
|
715
|
-
|
|
716
|
-
```tsx
|
|
717
|
-
import { createTheme, HazoAuthThemeProvider } from "hazo_auth/theme";
|
|
718
|
-
import { preset_indigo_sunset } from "hazo_auth/themes";
|
|
719
|
-
|
|
720
|
-
const theme = createTheme({
|
|
721
|
-
...preset_indigo_sunset,
|
|
722
|
-
brandPanel: { logoSrc: "/logo.svg", tagline: "Welcome." },
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
<HazoAuthThemeProvider theme={theme}>{children}</HazoAuthThemeProvider>
|
|
726
|
-
```
|
|
727
|
-
|
|
728
|
-
### Option 3: Build from scratch
|
|
729
|
-
|
|
730
|
-
```tsx
|
|
731
|
-
import { createTheme, HazoAuthThemeProvider } from "hazo_auth/theme";
|
|
732
|
-
|
|
733
|
-
const theme = createTheme({
|
|
734
|
-
colors: { primary: "#2563EB", primaryForeground: "#ffffff" },
|
|
735
|
-
layout: "split",
|
|
736
|
-
brandPanel: {
|
|
737
|
-
logoSrc: "/logo.svg",
|
|
738
|
-
tagline: "Welcome to MyApp.",
|
|
739
|
-
backgroundGradient: "linear-gradient(135deg, #2563EB, #7C3AED)",
|
|
740
|
-
},
|
|
741
|
-
});
|
|
742
|
-
|
|
743
|
-
<HazoAuthThemeProvider theme={theme}>{children}</HazoAuthThemeProvider>
|
|
744
|
-
```
|
|
745
|
-
|
|
746
|
-
**Note:** `preset_indigo_sunset` uses `var(--font-display)` and `var(--font-body)`. Wire these in `app/layout.tsx` using `next/font`. See [MIGRATION.md §3](./MIGRATION.md).
|
|
747
|
-
|
|
748
|
-
---
|
|
749
|
-
|
|
750
|
-
## Cookie Consent (v7.0.0+)
|
|
751
|
-
|
|
752
|
-
```tsx
|
|
753
|
-
import { CookieConsentBanner } from "hazo_auth/consent";
|
|
754
|
-
|
|
755
|
-
// In your root layout or a page:
|
|
756
|
-
<CookieConsentBanner />
|
|
757
|
-
|
|
758
|
-
// With GTM:
|
|
759
|
-
<CookieConsentBanner enableGTM gtmContainerId="GTM-XXXX" />
|
|
760
|
-
```
|
|
761
|
-
|
|
762
|
-
Read consent server-side:
|
|
763
|
-
|
|
764
|
-
```tsx
|
|
765
|
-
import { read_consent } from "hazo_auth/consent";
|
|
766
|
-
|
|
767
|
-
export async function GET(request: NextRequest) {
|
|
768
|
-
const consent = read_consent(request.headers);
|
|
769
|
-
if (consent.analytics) {
|
|
770
|
-
// Track event
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
```
|
|
774
|
-
|
|
775
|
-
Use the `useConsent` hook client-side:
|
|
776
|
-
|
|
777
|
-
```tsx
|
|
778
|
-
"use client";
|
|
779
|
-
import { useConsent } from "hazo_auth/consent";
|
|
780
|
-
|
|
781
|
-
export function MyComponent() {
|
|
782
|
-
const { analytics, marketing } = useConsent();
|
|
783
|
-
// Syncs across tabs via custom event
|
|
784
|
-
}
|
|
785
|
-
```
|
|
786
|
-
|
|
787
|
-
---
|
|
788
|
-
|
|
789
|
-
## Strings & Text Overrides (v7.0.0+)
|
|
790
|
-
|
|
791
|
-
INI text keys (`title`, `subtitle`, `cta_text`, `legal_text`) are removed in v7. Use `<HazoAuthStringsProvider>` or per-page props instead.
|
|
792
|
-
|
|
793
|
-
### Global overrides (app/layout.tsx)
|
|
794
|
-
|
|
795
|
-
```tsx
|
|
796
|
-
import { HazoAuthStringsProvider } from "hazo_auth/strings";
|
|
797
|
-
|
|
798
|
-
<HazoAuthStringsProvider strings={{ login: { title: "Sign in to MyApp" } }}>
|
|
799
|
-
{children}
|
|
800
|
-
</HazoAuthStringsProvider>
|
|
801
|
-
```
|
|
802
|
-
|
|
803
|
-
### Per-page overrides
|
|
804
|
-
|
|
805
|
-
```tsx
|
|
806
|
-
<LoginPage title="Sign in to MyApp" subtitle="Access your workspace" />
|
|
807
|
-
<RegisterPage title="Create your account" ctaText="Get started" />
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
`DEFAULT_STRINGS` is exported from `hazo_auth/strings` for reference.
|
|
811
|
-
|
|
812
|
-
---
|
|
813
|
-
|
|
814
511
|
## Configuration Setup
|
|
815
512
|
|
|
816
513
|
After installing the package, you need to set up configuration files in your project root:
|
|
@@ -1947,7 +1644,7 @@ export async function proxy(request: NextRequest) {
|
|
|
1947
1644
|
|
|
1948
1645
|
#### `hazo_get_tenant_auth` (Recommended for Multi-Tenant Apps)
|
|
1949
1646
|
|
|
1950
|
-
**New:** Tenant-aware authentication function that extracts scope context from request headers or cookies and returns enriched result
|
|
1647
|
+
**New:** Tenant-aware authentication function that extracts scope context from request headers or cookies and returns enriched result with organization information.
|
|
1951
1648
|
|
|
1952
1649
|
**Location:** `src/lib/auth/hazo_get_tenant_auth.server.ts`
|
|
1953
1650
|
|
|
@@ -1981,9 +1678,8 @@ type TenantAuthResult =
|
|
|
1981
1678
|
permissions: string[];
|
|
1982
1679
|
permission_ok: boolean;
|
|
1983
1680
|
missing_permissions?: string[];
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
user_scopes: ScopeDetails[]; // All user's scopes for switching
|
|
1681
|
+
organization: TenantOrganization | null; // NEW: Tenant context
|
|
1682
|
+
user_scopes: ScopeDetails[]; // NEW: All user's scopes for switching
|
|
1987
1683
|
scope_ok: boolean;
|
|
1988
1684
|
}
|
|
1989
1685
|
| {
|
|
@@ -1991,13 +1687,12 @@ type TenantAuthResult =
|
|
|
1991
1687
|
user: null;
|
|
1992
1688
|
permissions: [];
|
|
1993
1689
|
permission_ok: false;
|
|
1994
|
-
|
|
1995
|
-
selected_scope_id: null;
|
|
1690
|
+
organization: null;
|
|
1996
1691
|
user_scopes: [];
|
|
1997
1692
|
scope_ok: false;
|
|
1998
1693
|
};
|
|
1999
1694
|
|
|
2000
|
-
type
|
|
1695
|
+
type TenantOrganization = {
|
|
2001
1696
|
id: string;
|
|
2002
1697
|
name: string;
|
|
2003
1698
|
slug: string | null; // URL-friendly identifier
|
|
@@ -2039,10 +1734,10 @@ export async function GET(request: NextRequest) {
|
|
|
2039
1734
|
return NextResponse.json({ error: "Authentication required" }, { status: 401 });
|
|
2040
1735
|
}
|
|
2041
1736
|
|
|
2042
|
-
if (!auth.
|
|
1737
|
+
if (!auth.organization) {
|
|
2043
1738
|
return NextResponse.json(
|
|
2044
1739
|
{
|
|
2045
|
-
error: "No
|
|
1740
|
+
error: "No organization context",
|
|
2046
1741
|
available_scopes: auth.user_scopes.map(s => ({ id: s.id, name: s.name }))
|
|
2047
1742
|
},
|
|
2048
1743
|
{ status: 403 }
|
|
@@ -2050,10 +1745,10 @@ export async function GET(request: NextRequest) {
|
|
|
2050
1745
|
}
|
|
2051
1746
|
|
|
2052
1747
|
// Access tenant-specific data
|
|
2053
|
-
const data = await getTenantData(auth.
|
|
1748
|
+
const data = await getTenantData(auth.organization.id);
|
|
2054
1749
|
|
|
2055
1750
|
return NextResponse.json({
|
|
2056
|
-
|
|
1751
|
+
organization: auth.organization,
|
|
2057
1752
|
data,
|
|
2058
1753
|
// Include available scopes for UI scope switcher
|
|
2059
1754
|
available_scopes: auth.user_scopes,
|
|
@@ -2071,8 +1766,8 @@ export async function GET(request: NextRequest) {
|
|
|
2071
1766
|
required_permissions: ["view_reports"],
|
|
2072
1767
|
});
|
|
2073
1768
|
|
|
2074
|
-
// auth.
|
|
2075
|
-
const reports = await getReports(auth.
|
|
1769
|
+
// auth.organization is guaranteed non-null here
|
|
1770
|
+
const reports = await getReports(auth.organization.id);
|
|
2076
1771
|
return NextResponse.json({ reports });
|
|
2077
1772
|
} catch (error) {
|
|
2078
1773
|
if (error instanceof HazoAuthError) {
|
|
@@ -2122,16 +1817,16 @@ Helper function that wraps `hazo_get_tenant_auth` and throws typed errors for co
|
|
|
2122
1817
|
- `TenantRequiredError` (403) - No tenant context in request
|
|
2123
1818
|
- `TenantAccessDeniedError` (403) - User lacks access to requested tenant
|
|
2124
1819
|
|
|
2125
|
-
**Returns:** `RequiredTenantAuthResult` with guaranteed non-null `
|
|
1820
|
+
**Returns:** `RequiredTenantAuthResult` with guaranteed non-null `organization`
|
|
2126
1821
|
|
|
2127
1822
|
**Example:**
|
|
2128
1823
|
```typescript
|
|
2129
1824
|
export async function GET(request: NextRequest) {
|
|
2130
1825
|
try {
|
|
2131
|
-
//
|
|
2132
|
-
const {
|
|
1826
|
+
// organization is guaranteed to exist
|
|
1827
|
+
const { organization, user, permissions } = await require_tenant_auth(request);
|
|
2133
1828
|
|
|
2134
|
-
const data = await getData(
|
|
1829
|
+
const data = await getData(organization.id);
|
|
2135
1830
|
return NextResponse.json(data);
|
|
2136
1831
|
} catch (error) {
|
|
2137
1832
|
if (error instanceof HazoAuthError) {
|
|
@@ -2184,10 +1879,10 @@ export const DELETE = withAuth<{ id: string }>(
|
|
|
2184
1879
|
{ required_permissions: ["admin_system"] }
|
|
2185
1880
|
);
|
|
2186
1881
|
|
|
2187
|
-
// With tenant requirement (auth.
|
|
1882
|
+
// With tenant requirement (auth.organization guaranteed non-null)
|
|
2188
1883
|
export const GET = withAuth<{ id: string }>(
|
|
2189
1884
|
async (request, auth, { id }) => {
|
|
2190
|
-
const data = await getData(auth.
|
|
1885
|
+
const data = await getData(auth.organization.id, id);
|
|
2191
1886
|
return NextResponse.json(data);
|
|
2192
1887
|
},
|
|
2193
1888
|
{ require_tenant: true }
|
|
@@ -3283,3 +2978,51 @@ The `package.json` exports field defines the public API:
|
|
|
3283
2978
|
- Use `npx shadcn@latest add <component>` to scaffold new UI primitives.
|
|
3284
2979
|
- Centralize configurable values through `hazo_config`.
|
|
3285
2980
|
- Access backend resources exclusively via `hazo_connect`.
|
|
2981
|
+
|
|
2982
|
+
## Email OTP sign-in
|
|
2983
|
+
|
|
2984
|
+
```tsx
|
|
2985
|
+
// app/(auth)/otp/page.tsx
|
|
2986
|
+
import { OTPPage } from "hazo_auth/pages/otp";
|
|
2987
|
+
export default function Page(props) { return <OTPPage {...props} />; }
|
|
2988
|
+
```
|
|
2989
|
+
|
|
2990
|
+
Enable in config: `[hazo_auth__otp] enable_email_otp = true`. Run `npx hazo_auth migrate migrations/015_email_otp.sql`.
|
|
2991
|
+
|
|
2992
|
+
## Theming
|
|
2993
|
+
|
|
2994
|
+
```tsx
|
|
2995
|
+
import { HazoAuthThemeProvider, createTheme } from "hazo_auth/theme";
|
|
2996
|
+
|
|
2997
|
+
const my_theme = createTheme({
|
|
2998
|
+
colors: { primary: "#1D4ED8" },
|
|
2999
|
+
layout: "split",
|
|
3000
|
+
brandPanel: { logoSrc: "/logo.svg", tagline: "Welcome." },
|
|
3001
|
+
});
|
|
3002
|
+
|
|
3003
|
+
<HazoAuthThemeProvider theme={my_theme}>
|
|
3004
|
+
<LoginPage />
|
|
3005
|
+
</HazoAuthThemeProvider>
|
|
3006
|
+
```
|
|
3007
|
+
|
|
3008
|
+
Theme is auth-page scoped. Does not affect `:root`.
|
|
3009
|
+
|
|
3010
|
+
## Cookie Consent
|
|
3011
|
+
|
|
3012
|
+
```tsx
|
|
3013
|
+
import { CookieConsentBanner } from "hazo_auth/consent";
|
|
3014
|
+
// Place in your root layout:
|
|
3015
|
+
<CookieConsentBanner />
|
|
3016
|
+
```
|
|
3017
|
+
|
|
3018
|
+
Server-side consent check: `GET /api/hazo_auth/consent/me` returns `ConsentState`.
|
|
3019
|
+
|
|
3020
|
+
## String overrides
|
|
3021
|
+
|
|
3022
|
+
```tsx
|
|
3023
|
+
import { HazoAuthStringsProvider } from "hazo_auth/strings";
|
|
3024
|
+
|
|
3025
|
+
<HazoAuthStringsProvider strings={{ login: { title: "Welcome back" } }}>
|
|
3026
|
+
<LoginPage />
|
|
3027
|
+
</HazoAuthStringsProvider>
|
|
3028
|
+
```
|