hazo_auth 9.0.1 → 10.0.0
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 +39 -11
- package/SETUP_CHECKLIST.md +35 -16
- package/cli-src/cli/init_users.ts +40 -48
- package/cli-src/lib/auth/auth_types.ts +0 -2
- package/cli-src/lib/auth/hazo_get_auth.server.ts +31 -25
- package/cli-src/lib/auth/hazo_get_tenant_auth.server.ts +9 -13
- package/cli-src/lib/config/config_loader.server.ts +41 -3
- package/cli-src/lib/config/hazo_auth_core_config.ts +1 -1
- package/cli-src/lib/constants.ts +2 -0
- package/cli-src/lib/hazo_connect_setup.server.ts +20 -2
- package/cli-src/lib/profile_pic_menu_config.server.ts +4 -3
- package/cli-src/lib/schema/sqlite_schema.ts +0 -4
- package/cli-src/lib/scope_hierarchy_config.server.ts +1 -9
- package/cli-src/lib/services/invitation_service.ts +1 -1
- package/cli-src/lib/services/scope_service.ts +2 -76
- package/cli-src/lib/services/user_scope_service.ts +7 -61
- package/config/hazo_auth_config.example.ini +3 -1
- package/dist/cli/init_users.d.ts.map +1 -1
- package/dist/cli/init_users.js +42 -42
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -1
- package/dist/components/layouts/shared/components/profile_pic_menu.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/profile_pic_menu.js +7 -1
- package/dist/components/ui/input-otp.d.ts +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/auth/auth_types.d.ts +0 -2
- package/dist/lib/auth/auth_types.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_auth.server.js +27 -19
- package/dist/lib/auth/hazo_get_tenant_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_tenant_auth.server.js +10 -10
- package/dist/lib/config/config_loader.server.d.ts.map +1 -1
- package/dist/lib/config/config_loader.server.js +38 -3
- package/dist/lib/config/hazo_auth_core_config.js +1 -1
- package/dist/lib/constants.d.ts +1 -0
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +1 -0
- package/dist/lib/hazo_connect_setup.server.d.ts +1 -0
- package/dist/lib/hazo_connect_setup.server.d.ts.map +1 -1
- package/dist/lib/hazo_connect_setup.server.js +15 -2
- package/dist/lib/profile_pic_menu_config.server.d.ts +2 -1
- package/dist/lib/profile_pic_menu_config.server.d.ts.map +1 -1
- package/dist/lib/profile_pic_menu_config.server.js +1 -1
- package/dist/lib/schema/sqlite_schema.d.ts +1 -1
- package/dist/lib/schema/sqlite_schema.d.ts.map +1 -1
- package/dist/lib/schema/sqlite_schema.js +0 -4
- package/dist/lib/scope_hierarchy_config.server.d.ts +0 -2
- package/dist/lib/scope_hierarchy_config.server.d.ts.map +1 -1
- package/dist/lib/scope_hierarchy_config.server.js +1 -3
- package/dist/lib/services/invitation_service.d.ts +1 -1
- package/dist/lib/services/invitation_service.js +1 -1
- package/dist/lib/services/scope_service.d.ts +1 -14
- package/dist/lib/services/scope_service.d.ts.map +1 -1
- package/dist/lib/services/scope_service.js +2 -67
- package/dist/lib/services/user_scope_service.d.ts +5 -12
- package/dist/lib/services/user_scope_service.d.ts.map +1 -1
- package/dist/lib/services/user_scope_service.js +8 -45
- package/dist/server/routes/invitations.d.ts +1 -1
- package/dist/server/routes/invitations.d.ts.map +1 -1
- package/dist/server/routes/invitations.js +12 -11
- package/dist/server/routes/user_management_users.d.ts +1 -1
- package/package.json +15 -15
package/README.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
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 v9.1.1 🔧
|
|
6
|
+
|
|
7
|
+
**Dev-server noise fixes for Next.js 16 + Turbopack**
|
|
8
|
+
|
|
9
|
+
- `next.config.mjs`: `hazo_core` and `hazo_config` added to `serverExternalPackages`. Turbopack was bundling hazo_config and breaking `ini.parse()` CJS interop, causing every config-section read to throw and producing `config_loader_read_section_failed` ×9 + `me_endpoint_error` per request.
|
|
10
|
+
- `config/hazo_auth_config.ini`: renamed `[log.overrides]` → `[log_overrides]`. The `ini` v4 library parses dots in section names as nesting separators, creating null-prototype objects that hazo_config cannot stringify. Dotted section names must be avoided.
|
|
11
|
+
- `src/lib/config/config_loader.server.ts`: `HazoConfig` instances are now memoized per resolved file path — one INI parse per server process instead of one per getter call.
|
|
12
|
+
- Companion fixes in hazo_core@1.0.3 (registerSingleton moved to module level, hazo_debug probe → lazy import) and hazo_config@2.1.9 (hazo_core/errors probe → lazy import, graceful skip of null-prototype nested objects in refresh()).
|
|
13
|
+
|
|
14
|
+
> **Action required if you use `[log.overrides]` in your app's config:** rename it to `[log_overrides]` (or any non-dotted name). This applies to any hazo_config-backed INI file, not just hazo_auth.
|
|
15
|
+
|
|
5
16
|
### What's New in v8.0.1 🔧
|
|
6
17
|
|
|
7
18
|
**Auto-test and middleware bug fixes**
|
|
@@ -207,7 +218,7 @@ See [CHANGE_LOG.md](./CHANGE_LOG.md) for detailed migration guide, rationale, an
|
|
|
207
218
|
|
|
208
219
|
```bash
|
|
209
220
|
# Install hazo_auth and required peer dependencies
|
|
210
|
-
npm install hazo_auth hazo_config hazo_connect hazo_logs next react react-dom next-auth
|
|
221
|
+
npm install hazo_auth hazo_core hazo_config hazo_connect hazo_logs next react react-dom next-auth
|
|
211
222
|
|
|
212
223
|
# UI peer dependencies (required if using hazo_auth UI components)
|
|
213
224
|
npm install hazo_ui lucide-react sonner next-themes @radix-ui/react-accordion @radix-ui/react-alert-dialog @radix-ui/react-avatar @radix-ui/react-checkbox @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-hover-card @radix-ui/react-label @radix-ui/react-select @radix-ui/react-separator @radix-ui/react-slot @radix-ui/react-switch @radix-ui/react-tabs @radix-ui/react-tooltip
|
|
@@ -231,7 +242,7 @@ The fastest way to get started is using the CLI commands:
|
|
|
231
242
|
|
|
232
243
|
```bash
|
|
233
244
|
# 1. Install the package and peer dependencies
|
|
234
|
-
npm install hazo_auth hazo_config hazo_connect hazo_logs next react react-dom next-auth
|
|
245
|
+
npm install hazo_auth hazo_core hazo_config hazo_connect hazo_logs next react react-dom next-auth
|
|
235
246
|
|
|
236
247
|
# 2. Initialize your project (directories, config, database, images)
|
|
237
248
|
npx hazo_auth init
|
|
@@ -425,7 +436,7 @@ import { hazo_get_auth } from "hazo_auth/lib/auth/hazo_get_auth.server";
|
|
|
425
436
|
|
|
426
437
|
**Peer Dependencies (Required):**
|
|
427
438
|
```bash
|
|
428
|
-
npm install hazo_config hazo_connect hazo_logs
|
|
439
|
+
npm install hazo_core hazo_config hazo_connect hazo_logs
|
|
429
440
|
```
|
|
430
441
|
|
|
431
442
|
**UI Components:** All shadcn/ui components are bundled with hazo_auth. You do NOT need to install them separately.
|
|
@@ -731,9 +742,7 @@ CREATE INDEX idx_hazo_scopes_level ON hazo_scopes(level);
|
|
|
731
742
|
CREATE INDEX idx_hazo_scopes_slug ON hazo_scopes(slug);
|
|
732
743
|
|
|
733
744
|
-- 7a. Reserved system scopes
|
|
734
|
-
|
|
735
|
-
VALUES ('00000000-0000-0000-0000-000000000000', NULL, 'Super Admin', 'system')
|
|
736
|
-
ON CONFLICT (id) DO NOTHING;
|
|
745
|
+
-- Note: Super Admin scope retired in v10; use hazo_org_global_admin permission instead (see migration 020)
|
|
737
746
|
INSERT INTO hazo_scopes (id, parent_id, name, level)
|
|
738
747
|
VALUES ('00000000-0000-0000-0000-000000000001', NULL, 'System', 'default')
|
|
739
748
|
ON CONFLICT (id) DO NOTHING;
|
|
@@ -889,8 +898,7 @@ CREATE INDEX IF NOT EXISTS idx_hazo_scopes_level ON hazo_scopes(level);
|
|
|
889
898
|
CREATE INDEX IF NOT EXISTS idx_hazo_scopes_slug ON hazo_scopes(slug);
|
|
890
899
|
|
|
891
900
|
-- Reserved system scopes
|
|
892
|
-
|
|
893
|
-
VALUES ('00000000-0000-0000-0000-000000000000', NULL, 'Super Admin', 'system', datetime('now'), datetime('now'));
|
|
901
|
+
-- Note: Super Admin scope retired in v10; use hazo_org_global_admin permission instead (see migration 020)
|
|
894
902
|
INSERT OR IGNORE INTO hazo_scopes (id, parent_id, name, level, created_at, changed_at)
|
|
895
903
|
VALUES ('00000000-0000-0000-0000-000000000001', NULL, 'System', 'default', datetime('now'), datetime('now'));
|
|
896
904
|
|
|
@@ -1732,7 +1740,6 @@ type TenantOrganization = {
|
|
|
1732
1740
|
slug: string | null; // URL-friendly identifier
|
|
1733
1741
|
level: string; // "Company", "Division", etc.
|
|
1734
1742
|
role_id: string; // User's role in this scope
|
|
1735
|
-
is_super_admin: boolean;
|
|
1736
1743
|
branding?: {
|
|
1737
1744
|
logo_url: string | null;
|
|
1738
1745
|
primary_color: string | null;
|
|
@@ -2489,6 +2496,28 @@ import { ProfilePicMenu } from "hazo_auth/components/layouts/shared";
|
|
|
2489
2496
|
/>
|
|
2490
2497
|
```
|
|
2491
2498
|
|
|
2499
|
+
#### Menu item types
|
|
2500
|
+
|
|
2501
|
+
`custom_menu_items` accepts four `type` values:
|
|
2502
|
+
|
|
2503
|
+
| Type | Renders as | Fields used |
|
|
2504
|
+
|------|------------|-------------|
|
|
2505
|
+
| `info` | Read-only label/value row | `label`, `value` |
|
|
2506
|
+
| `link` | Navigation link | `label`, `href` |
|
|
2507
|
+
| `separator` | Divider | — |
|
|
2508
|
+
| `action` | Clickable item that fires a client-side callback | `label`, `onSelect` |
|
|
2509
|
+
|
|
2510
|
+
```typescript
|
|
2511
|
+
<ProfilePicMenu
|
|
2512
|
+
variant="dropdown"
|
|
2513
|
+
custom_menu_items={[
|
|
2514
|
+
{ type: "action", label: "Switch workspace", onSelect: () => openSwitcher(), order: 1, id: "switch" }
|
|
2515
|
+
]}
|
|
2516
|
+
/>
|
|
2517
|
+
```
|
|
2518
|
+
|
|
2519
|
+
> **`action` items are React-only.** Because `onSelect` is a function, action items cannot be expressed via the INI `custom_menu_items` config (which only supports the serialisable `info` / `link` / `separator` types) — pass them through the `custom_menu_items` prop instead.
|
|
2520
|
+
|
|
2492
2521
|
### Configuration
|
|
2493
2522
|
|
|
2494
2523
|
```ini
|
|
@@ -2879,8 +2908,7 @@ Retrieves basic profile information for multiple users in a single batch call.
|
|
|
2879
2908
|
**Location:** `src/lib/services/user_profiles_service.ts`
|
|
2880
2909
|
|
|
2881
2910
|
```typescript
|
|
2882
|
-
import { hazo_get_user_profiles } from "hazo_auth/lib
|
|
2883
|
-
import { get_hazo_connect_instance } from "hazo_auth/server/hazo_connect_instance.server";
|
|
2911
|
+
import { hazo_get_user_profiles, get_hazo_connect_instance } from "hazo_auth/server-lib";
|
|
2884
2912
|
|
|
2885
2913
|
export async function GET(request: NextRequest) {
|
|
2886
2914
|
const adapter = get_hazo_connect_instance();
|
package/SETUP_CHECKLIST.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
This checklist provides step-by-step instructions for setting up the `hazo_auth` package in your Next.js project. AI assistants can follow this guide to ensure complete and correct setup.
|
|
4
4
|
|
|
5
|
+
## v9.1.1 — Next.js 16 / Turbopack setup (new installs and migrations)
|
|
6
|
+
|
|
7
|
+
### Add to `serverExternalPackages`
|
|
8
|
+
|
|
9
|
+
hazo_core and hazo_config use CJS dependencies (`ini`, optional peer probes) that Turbopack cannot bundle cleanly. Add them to the external packages list in `next.config.mjs` / `next.config.js`:
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
const nextConfig = {
|
|
13
|
+
serverExternalPackages: [
|
|
14
|
+
"hazo_notify", "argon2",
|
|
15
|
+
"hazo_core", "hazo_config", // required for Next 16 + Turbopack
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Avoid dotted section names in INI config files
|
|
21
|
+
|
|
22
|
+
`ini` v4 parses dots in section headers as nesting separators (`[log.overrides]` becomes `parsed.log.overrides`). hazo_config uses flat sections — use underscores instead:
|
|
23
|
+
|
|
24
|
+
```ini
|
|
25
|
+
# Bad: creates a nested null-prototype object that hazo_config cannot stringify
|
|
26
|
+
[log.overrides]
|
|
27
|
+
|
|
28
|
+
# Good:
|
|
29
|
+
[log_overrides]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
5
34
|
## v8.0.0 Migration (from v7.x)
|
|
6
35
|
|
|
7
36
|
### Breaking changes
|
|
@@ -515,9 +544,7 @@ CREATE INDEX IF NOT EXISTS idx_hazo_scopes_level ON hazo_scopes(level);
|
|
|
515
544
|
CREATE INDEX IF NOT EXISTS idx_hazo_scopes_slug ON hazo_scopes(slug);
|
|
516
545
|
|
|
517
546
|
-- Reserved system scopes
|
|
518
|
-
|
|
519
|
-
VALUES ('00000000-0000-0000-0000-000000000000', NULL, 'Super Admin', 'system', datetime('now'), datetime('now'));
|
|
520
|
-
|
|
547
|
+
-- Note: Super Admin scope retired in v10; use hazo_org_global_admin permission instead (see migration 020)
|
|
521
548
|
INSERT OR IGNORE INTO hazo_scopes (id, parent_id, name, level, created_at, changed_at)
|
|
522
549
|
VALUES ('00000000-0000-0000-0000-000000000001', NULL, 'System', 'default', datetime('now'), datetime('now'));
|
|
523
550
|
|
|
@@ -704,10 +731,7 @@ CREATE INDEX idx_hazo_scopes_level ON hazo_scopes(level);
|
|
|
704
731
|
CREATE INDEX idx_hazo_scopes_slug ON hazo_scopes(slug);
|
|
705
732
|
|
|
706
733
|
-- 7a. Reserved system scopes
|
|
707
|
-
|
|
708
|
-
VALUES ('00000000-0000-0000-0000-000000000000', NULL, 'Super Admin', 'system', NOW(), NOW())
|
|
709
|
-
ON CONFLICT (id) DO NOTHING;
|
|
710
|
-
|
|
734
|
+
-- Note: Super Admin scope retired in v10; use hazo_org_global_admin permission instead (see migration 020)
|
|
711
735
|
INSERT INTO hazo_scopes (id, parent_id, name, level, created_at, changed_at)
|
|
712
736
|
VALUES ('00000000-0000-0000-0000-000000000001', NULL, 'System', 'default', NOW(), NOW())
|
|
713
737
|
ON CONFLICT (id) DO NOTHING;
|
|
@@ -868,8 +892,8 @@ GRANT USAGE ON TYPE hazo_enum_invitation_status TO anon, authenticated;
|
|
|
868
892
|
- [ ] `hazo_invitations`
|
|
869
893
|
- [ ] `hazo_user_relationships` (managed sub-profile parent/child links)
|
|
870
894
|
- [ ] Reserved system scopes inserted:
|
|
871
|
-
- [ ] `00000000-0000-0000-0000-000000000000` (Super Admin)
|
|
872
895
|
- [ ] `00000000-0000-0000-0000-000000000001` (System / non-multi-tenancy default)
|
|
896
|
+
- [ ] Migration 020 run: Super Admin scope retired, `hazo_org_global_admin` permission granted to affected roles
|
|
873
897
|
- [ ] `firm_admin` role inserted into `hazo_roles`
|
|
874
898
|
|
|
875
899
|
---
|
|
@@ -1739,10 +1763,7 @@ CREATE INDEX IF NOT EXISTS idx_hazo_scopes_level ON hazo_scopes(level);
|
|
|
1739
1763
|
CREATE INDEX IF NOT EXISTS idx_hazo_scopes_slug ON hazo_scopes(slug);
|
|
1740
1764
|
|
|
1741
1765
|
-- 3. Create system scopes
|
|
1742
|
-
|
|
1743
|
-
VALUES ('00000000-0000-0000-0000-000000000000', NULL, 'Super Admin', 'system', NOW(), NOW())
|
|
1744
|
-
ON CONFLICT (id) DO NOTHING;
|
|
1745
|
-
|
|
1766
|
+
-- Note: Super Admin scope retired in v10; use hazo_org_global_admin permission instead (see migration 020)
|
|
1746
1767
|
INSERT INTO hazo_scopes (id, parent_id, name, level, created_at, changed_at)
|
|
1747
1768
|
VALUES ('00000000-0000-0000-0000-000000000001', NULL, 'System', 'default', NOW(), NOW())
|
|
1748
1769
|
ON CONFLICT (id) DO NOTHING;
|
|
@@ -1832,9 +1853,7 @@ CREATE INDEX IF NOT EXISTS idx_hazo_scopes_level ON hazo_scopes(level);
|
|
|
1832
1853
|
CREATE INDEX IF NOT EXISTS idx_hazo_scopes_slug ON hazo_scopes(slug);
|
|
1833
1854
|
|
|
1834
1855
|
-- 3. Create system scopes
|
|
1835
|
-
|
|
1836
|
-
VALUES ('00000000-0000-0000-0000-000000000000', NULL, 'Super Admin', 'system', datetime('now'), datetime('now'));
|
|
1837
|
-
|
|
1856
|
+
-- Note: Super Admin scope retired in v10; use hazo_org_global_admin permission instead (see migration 020)
|
|
1838
1857
|
INSERT OR IGNORE INTO hazo_scopes (id, parent_id, name, level, created_at, changed_at)
|
|
1839
1858
|
VALUES ('00000000-0000-0000-0000-000000000001', NULL, 'System', 'default', datetime('now'), datetime('now'));
|
|
1840
1859
|
|
|
@@ -1930,8 +1949,8 @@ CREATE INDEX IF NOT EXISTS idx_hazo_scopes_slug ON hazo_scopes(slug);
|
|
|
1930
1949
|
- [ ] `hazo_user_scopes` (user-scope-role assignments)
|
|
1931
1950
|
- [ ] `hazo_invitations` (user invitation flow)
|
|
1932
1951
|
- [ ] System scopes exist:
|
|
1933
|
-
- [ ] Super Admin scope (00000000-0000-0000-0000-000000000000)
|
|
1934
1952
|
- [ ] Default System scope (00000000-0000-0000-0000-000000000001)
|
|
1953
|
+
- [ ] Migration 020 run: Super Admin scope retired, `hazo_org_global_admin` permission granted
|
|
1935
1954
|
- [ ] Grants applied (PostgreSQL)
|
|
1936
1955
|
- [ ] HRBAC tabs visible in User Management
|
|
1937
1956
|
- [ ] Scope test page works
|
|
@@ -5,7 +5,8 @@ import { createCrudService } from "hazo_connect/server";
|
|
|
5
5
|
import { get_user_management_config } from "../lib/user_management_config.server.js";
|
|
6
6
|
import { get_config_value } from "../lib/config/config_loader.server.js";
|
|
7
7
|
import { create_app_logger } from "../lib/app_logger.js";
|
|
8
|
-
import {
|
|
8
|
+
import { DEFAULT_SYSTEM_SCOPE_ID } from "../lib/services/scope_service.js";
|
|
9
|
+
import { GLOBAL_ADMIN_PERMISSION } from "../lib/constants.js";
|
|
9
10
|
|
|
10
11
|
// section: types
|
|
11
12
|
type InitSummary = {
|
|
@@ -23,10 +24,6 @@ type InitSummary = {
|
|
|
23
24
|
existing: number;
|
|
24
25
|
};
|
|
25
26
|
// v5.x: Removed user_role - roles are now assigned via hazo_user_scopes
|
|
26
|
-
super_admin_scope: {
|
|
27
|
-
inserted: boolean;
|
|
28
|
-
existing: boolean;
|
|
29
|
-
};
|
|
30
27
|
user_scope: {
|
|
31
28
|
inserted: boolean;
|
|
32
29
|
existing: boolean;
|
|
@@ -77,23 +74,13 @@ function print_summary(summary: InitSummary): void {
|
|
|
77
74
|
|
|
78
75
|
// v5.x: User-Role assignments are now handled via User-Scope assignments (see below)
|
|
79
76
|
|
|
80
|
-
// Super admin scope summary
|
|
81
|
-
console.log("Super Admin Scope:");
|
|
82
|
-
if (summary.super_admin_scope.inserted) {
|
|
83
|
-
console.log(` ✓ Inserted: Super Admin scope (ID: ${SUPER_ADMIN_SCOPE_ID})`);
|
|
84
|
-
}
|
|
85
|
-
if (summary.super_admin_scope.existing) {
|
|
86
|
-
console.log(` ⊙ Already existed: Super Admin scope (ID: ${SUPER_ADMIN_SCOPE_ID})`);
|
|
87
|
-
}
|
|
88
|
-
console.log();
|
|
89
|
-
|
|
90
77
|
// User scope summary
|
|
91
78
|
console.log("User-Scope Assignment:");
|
|
92
79
|
if (summary.user_scope.inserted) {
|
|
93
|
-
console.log(` ✓ Inserted: User assigned to
|
|
80
|
+
console.log(` ✓ Inserted: User assigned to default system scope`);
|
|
94
81
|
}
|
|
95
82
|
if (summary.user_scope.existing) {
|
|
96
|
-
console.log(` ⊙ Already existed: User already in
|
|
83
|
+
console.log(` ⊙ Already existed: User already in default system scope`);
|
|
97
84
|
}
|
|
98
85
|
console.log();
|
|
99
86
|
|
|
@@ -131,10 +118,6 @@ export async function handle_init_users(options: InitUsersOptions = {}): Promise
|
|
|
131
118
|
existing: 0,
|
|
132
119
|
},
|
|
133
120
|
// v5.x: Removed user_role - roles are now assigned via hazo_user_scopes
|
|
134
|
-
super_admin_scope: {
|
|
135
|
-
inserted: false,
|
|
136
|
-
existing: false,
|
|
137
|
-
},
|
|
138
121
|
user_scope: {
|
|
139
122
|
inserted: false,
|
|
140
123
|
existing: false,
|
|
@@ -155,7 +138,6 @@ export async function handle_init_users(options: InitUsersOptions = {}): Promise
|
|
|
155
138
|
});
|
|
156
139
|
const users_service = createCrudService(hazoConnect, "hazo_users");
|
|
157
140
|
// v5.x: Removed hazo_user_roles - roles are now assigned via hazo_user_scopes
|
|
158
|
-
const scopes_service = createCrudService(hazoConnect, "hazo_scopes");
|
|
159
141
|
// hazo_user_scopes uses composite primary key (user_id, scope_id), no 'id' column
|
|
160
142
|
const user_scopes_service = createCrudService(hazoConnect, "hazo_user_scopes", {
|
|
161
143
|
primaryKeys: ["user_id", "scope_id"],
|
|
@@ -317,54 +299,63 @@ export async function handle_init_users(options: InitUsersOptions = {}): Promise
|
|
|
317
299
|
console.log(`✓ Found user: ${super_user_email} (ID: ${user_id})`);
|
|
318
300
|
console.log();
|
|
319
301
|
|
|
320
|
-
//
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
} else {
|
|
329
|
-
await scopes_service.insert({
|
|
330
|
-
id: SUPER_ADMIN_SCOPE_ID,
|
|
331
|
-
parent_id: null,
|
|
332
|
-
name: "Super Admin",
|
|
333
|
-
level: "system",
|
|
302
|
+
// 7. Ensure hazo_org_global_admin is in the permission catalog
|
|
303
|
+
const global_admin_perms = await permissions_service.findBy({
|
|
304
|
+
permission_name: GLOBAL_ADMIN_PERMISSION,
|
|
305
|
+
});
|
|
306
|
+
if (!Array.isArray(global_admin_perms) || global_admin_perms.length === 0) {
|
|
307
|
+
await permissions_service.insert({
|
|
308
|
+
permission_name: GLOBAL_ADMIN_PERMISSION,
|
|
309
|
+
description: "Global admin — access to all scopes and operations",
|
|
334
310
|
created_at: now,
|
|
335
311
|
changed_at: now,
|
|
336
312
|
});
|
|
337
|
-
|
|
338
|
-
|
|
313
|
+
console.log(`✓ Created permission: ${GLOBAL_ADMIN_PERMISSION}`);
|
|
314
|
+
} else {
|
|
315
|
+
console.log(`✓ Permission already exists: ${GLOBAL_ADMIN_PERMISSION}`);
|
|
339
316
|
}
|
|
317
|
+
console.log();
|
|
340
318
|
|
|
319
|
+
// 9. Ensure hazo_org_global_admin is assigned to the super user role
|
|
320
|
+
// (The role already has all configured permissions; this ensures the global admin perm is included)
|
|
321
|
+
const perm_row = await permissions_service.findBy({ permission_name: GLOBAL_ADMIN_PERMISSION });
|
|
322
|
+
const perm_id = Array.isArray(perm_row) && perm_row.length > 0 ? (perm_row[0].id as string) : null;
|
|
323
|
+
if (perm_id && role_id) {
|
|
324
|
+
const existing_rp = await role_permissions_service.findBy({ role_id, permission_id: perm_id });
|
|
325
|
+
if (!Array.isArray(existing_rp) || existing_rp.length === 0) {
|
|
326
|
+
await role_permissions_service.insert({ role_id, permission_id: perm_id });
|
|
327
|
+
console.log(`✓ Assigned ${GLOBAL_ADMIN_PERMISSION} to super user role`);
|
|
328
|
+
} else {
|
|
329
|
+
console.log(`✓ Super user role already has ${GLOBAL_ADMIN_PERMISSION}`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
341
332
|
console.log();
|
|
342
333
|
|
|
343
|
-
//
|
|
334
|
+
// 10. Assign user to DEFAULT_SYSTEM_SCOPE_ID (global access comes from the permission, not the scope)
|
|
344
335
|
const existing_user_scopes = await user_scopes_service.findBy({
|
|
345
336
|
user_id,
|
|
346
|
-
scope_id:
|
|
337
|
+
scope_id: DEFAULT_SYSTEM_SCOPE_ID,
|
|
347
338
|
});
|
|
348
339
|
|
|
349
340
|
if (Array.isArray(existing_user_scopes) && existing_user_scopes.length > 0) {
|
|
350
341
|
summary.user_scope.existing = true;
|
|
351
|
-
console.log(`✓ User already assigned to
|
|
342
|
+
console.log(`✓ User already assigned to default system scope`);
|
|
352
343
|
} else {
|
|
353
344
|
await user_scopes_service.insert({
|
|
354
345
|
user_id,
|
|
355
|
-
scope_id:
|
|
356
|
-
root_scope_id:
|
|
346
|
+
scope_id: DEFAULT_SYSTEM_SCOPE_ID,
|
|
347
|
+
root_scope_id: DEFAULT_SYSTEM_SCOPE_ID,
|
|
357
348
|
role_id,
|
|
358
349
|
created_at: now,
|
|
359
350
|
changed_at: now,
|
|
360
351
|
});
|
|
361
352
|
summary.user_scope.inserted = true;
|
|
362
|
-
console.log(`✓ Assigned user to
|
|
353
|
+
console.log(`✓ Assigned user to default system scope`);
|
|
363
354
|
}
|
|
364
355
|
|
|
365
356
|
console.log();
|
|
366
357
|
|
|
367
|
-
//
|
|
358
|
+
// 11. Print summary
|
|
368
359
|
print_summary(summary);
|
|
369
360
|
|
|
370
361
|
logger.info("init_users_completed", {
|
|
@@ -402,15 +393,16 @@ export function show_init_users_help(): void {
|
|
|
402
393
|
console.log(`
|
|
403
394
|
hazo_auth init-users
|
|
404
395
|
|
|
405
|
-
Initialize users, roles,
|
|
396
|
+
Initialize users, roles, and permissions from configuration.
|
|
406
397
|
|
|
407
398
|
This command reads from hazo_auth_config.ini and:
|
|
408
399
|
1. Creates permissions from [hazo_auth__user_management] application_permission_list_defaults
|
|
409
400
|
2. Creates a 'default_super_user_role' role
|
|
410
401
|
3. Assigns all permissions to the super user role
|
|
411
402
|
4. Finds user by email (from --email parameter or config)
|
|
412
|
-
5.
|
|
413
|
-
6. Assigns the user to the
|
|
403
|
+
5. Ensures the '${GLOBAL_ADMIN_PERMISSION}' permission exists and is assigned to the super user role
|
|
404
|
+
6. Assigns the user to the default system scope (${DEFAULT_SYSTEM_SCOPE_ID})
|
|
405
|
+
Global admin access is granted via the '${GLOBAL_ADMIN_PERMISSION}' permission, not by scope
|
|
414
406
|
(v5.x: Roles are assigned per-scope via hazo_user_scopes table)
|
|
415
407
|
|
|
416
408
|
Options:
|
|
@@ -24,7 +24,6 @@ export type HazoAuthUser = {
|
|
|
24
24
|
export type ScopeAccessInfo = {
|
|
25
25
|
scope_id: string;
|
|
26
26
|
scope_name?: string;
|
|
27
|
-
is_super_admin?: boolean;
|
|
28
27
|
};
|
|
29
28
|
|
|
30
29
|
/**
|
|
@@ -135,7 +134,6 @@ export type TenantOrganization = {
|
|
|
135
134
|
slug: string | null;
|
|
136
135
|
level: string;
|
|
137
136
|
role_id: string;
|
|
138
|
-
is_super_admin: boolean;
|
|
139
137
|
branding?: {
|
|
140
138
|
logo_url: string | null;
|
|
141
139
|
primary_color: string | null;
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from "../services/user_scope_service.js";
|
|
31
31
|
import { get_cookie_name, BASE_COOKIE_NAMES } from "../cookies_config.server.js";
|
|
32
32
|
import { get_app_permission_descriptions } from "../app_permissions_config.server.js";
|
|
33
|
+
import { GLOBAL_ADMIN_PERMISSION } from "../constants.js";
|
|
33
34
|
|
|
34
35
|
// section: helpers
|
|
35
36
|
|
|
@@ -383,7 +384,6 @@ async function check_scope_access_internal(
|
|
|
383
384
|
scope_access_via: {
|
|
384
385
|
scope_id: result.access_via.scope_id,
|
|
385
386
|
scope_name: result.access_via.scope_name,
|
|
386
|
-
is_super_admin: result.is_super_admin,
|
|
387
387
|
},
|
|
388
388
|
user_scopes,
|
|
389
389
|
};
|
|
@@ -587,31 +587,37 @@ export async function hazo_get_auth(
|
|
|
587
587
|
const hrbac_enabled = is_hrbac_enabled();
|
|
588
588
|
|
|
589
589
|
if (hrbac_enabled && options?.scope_id) {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
590
|
+
// Global admin permission grants access to all scopes
|
|
591
|
+
const has_global_admin = permissions.includes(GLOBAL_ADMIN_PERMISSION);
|
|
592
|
+
if (has_global_admin) {
|
|
593
|
+
scope_ok = true;
|
|
594
|
+
scope_access_via = { scope_id: options.scope_id };
|
|
595
|
+
} else {
|
|
596
|
+
const scope_result = await check_scope_access_internal(
|
|
597
|
+
user.id,
|
|
598
|
+
options.scope_id,
|
|
599
|
+
);
|
|
600
|
+
scope_ok = scope_result.scope_ok;
|
|
601
|
+
scope_access_via = scope_result.scope_access_via;
|
|
602
|
+
|
|
603
|
+
// Log scope denial if permission logging is enabled
|
|
604
|
+
if (!scope_ok && config.log_permission_denials) {
|
|
605
|
+
const client_ip = get_client_ip(request);
|
|
606
|
+
logger.warn("auth_utility_scope_access_denied", {
|
|
607
|
+
filename: get_filename(),
|
|
608
|
+
line_number: get_line_number(),
|
|
609
|
+
user_id: user.id,
|
|
610
|
+
scope_id: options.scope_id,
|
|
611
|
+
user_scopes: scope_result.user_scopes,
|
|
612
|
+
ip: client_ip,
|
|
613
|
+
correlation_id: getCorrelationId(),
|
|
614
|
+
});
|
|
615
|
+
}
|
|
611
616
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
617
|
+
// Throw error if strict mode and scope access denied
|
|
618
|
+
if (!scope_ok && options.strict) {
|
|
619
|
+
throw new ScopeAccessError(options.scope_id, scope_result.user_scopes);
|
|
620
|
+
}
|
|
615
621
|
}
|
|
616
622
|
}
|
|
617
623
|
|
|
@@ -7,6 +7,7 @@ import { NextRequest } from "next/server";
|
|
|
7
7
|
import { hazo_get_auth } from "./hazo_get_auth.server.js";
|
|
8
8
|
import { get_auth_cache } from "./auth_cache.js";
|
|
9
9
|
import { get_scope_by_id } from "../services/scope_service.js";
|
|
10
|
+
import { GLOBAL_ADMIN_PERMISSION } from "../constants.js";
|
|
10
11
|
import { get_hazo_connect_instance } from "../hazo_connect_instance.server.js";
|
|
11
12
|
import { get_cookie_name } from "../cookies_config.server.js";
|
|
12
13
|
import { get_auth_utility_config } from "../auth_utility_config.server.js";
|
|
@@ -62,14 +63,12 @@ export function extract_scope_id_from_request(
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
/**
|
|
65
|
-
* Builds TenantOrganization from scope details
|
|
66
|
+
* Builds TenantOrganization from scope details
|
|
66
67
|
* @param scope_details - Full scope details from cache
|
|
67
|
-
* @param is_super_admin - Whether user is accessing as super admin
|
|
68
68
|
* @returns TenantOrganization object
|
|
69
69
|
*/
|
|
70
70
|
function build_tenant_organization(
|
|
71
71
|
scope_details: ScopeDetails,
|
|
72
|
-
is_super_admin: boolean,
|
|
73
72
|
): TenantOrganization {
|
|
74
73
|
return {
|
|
75
74
|
id: scope_details.id,
|
|
@@ -77,7 +76,6 @@ function build_tenant_organization(
|
|
|
77
76
|
slug: scope_details.slug,
|
|
78
77
|
level: scope_details.level,
|
|
79
78
|
role_id: scope_details.role_id,
|
|
80
|
-
is_super_admin,
|
|
81
79
|
branding:
|
|
82
80
|
scope_details.logo_url || scope_details.primary_color
|
|
83
81
|
? {
|
|
@@ -159,18 +157,17 @@ export async function hazo_get_tenant_auth(
|
|
|
159
157
|
let organization: TenantOrganization | null = null;
|
|
160
158
|
|
|
161
159
|
if (scope_id && auth_result.scope_ok && auth_result.scope_access_via) {
|
|
162
|
-
//
|
|
160
|
+
// Try to find the scope in user's cached scope assignments first.
|
|
161
|
+
// For global admins the scope may not be in their cache (they can access any scope),
|
|
162
|
+
// in which case we fall through to the permission-based fetch below.
|
|
163
163
|
const access_scope = user_scopes.find(
|
|
164
164
|
(s) => s.id === auth_result.scope_access_via?.scope_id,
|
|
165
165
|
);
|
|
166
166
|
|
|
167
167
|
if (access_scope) {
|
|
168
|
-
organization = build_tenant_organization(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
);
|
|
172
|
-
} else if (auth_result.scope_access_via.is_super_admin) {
|
|
173
|
-
// Super admin accessing scope they're not assigned to - fetch scope details
|
|
168
|
+
organization = build_tenant_organization(access_scope);
|
|
169
|
+
} else if (auth_result.permissions.includes(GLOBAL_ADMIN_PERMISSION)) {
|
|
170
|
+
// Global admin accessing a scope they aren't directly assigned to — fetch scope details
|
|
174
171
|
const hazoConnect = get_hazo_connect_instance();
|
|
175
172
|
const scope_result = await get_scope_by_id(hazoConnect, scope_id);
|
|
176
173
|
if (scope_result.success && scope_result.scope) {
|
|
@@ -179,8 +176,7 @@ export async function hazo_get_tenant_auth(
|
|
|
179
176
|
name: scope_result.scope.name,
|
|
180
177
|
slug: null, // Could fetch from scope if slug column exists
|
|
181
178
|
level: scope_result.scope.level,
|
|
182
|
-
role_id: "", //
|
|
183
|
-
is_super_admin: true,
|
|
179
|
+
role_id: "", // Global admin doesn't have a role assignment in the scope
|
|
184
180
|
branding: scope_result.scope.logo_url
|
|
185
181
|
? {
|
|
186
182
|
logo_url: scope_result.scope.logo_url,
|
|
@@ -11,6 +11,15 @@ import { create_app_logger } from "../app_logger.js";
|
|
|
11
11
|
// section: constants
|
|
12
12
|
const DEFAULT_CONFIG_FILE = "config/hazo_auth_config.ini";
|
|
13
13
|
|
|
14
|
+
// section: config_instance_cache
|
|
15
|
+
// Cache HazoConfig instances by resolved file path so we never construct (and re-parse)
|
|
16
|
+
// more than one instance per config file per server process. This prevents repeated
|
|
17
|
+
// warnings when multiple getters read the same file in a single request, and eliminates
|
|
18
|
+
// redundant synchronous file I/O.
|
|
19
|
+
const _config_instance_cache = new Map<string, HazoConfig>();
|
|
20
|
+
// Track paths that failed to construct so we don't retry and warn repeatedly.
|
|
21
|
+
const _config_failed_paths = new Set<string>();
|
|
22
|
+
|
|
14
23
|
// section: helpers
|
|
15
24
|
/**
|
|
16
25
|
* Gets the default config file path
|
|
@@ -24,6 +33,35 @@ function get_config_file_path(custom_path?: string): string {
|
|
|
24
33
|
return path.resolve(process.cwd(), DEFAULT_CONFIG_FILE);
|
|
25
34
|
}
|
|
26
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Returns a cached HazoConfig instance for the given path, constructing one if needed.
|
|
38
|
+
* Returns null if construction fails (logs the error once on first failure).
|
|
39
|
+
*/
|
|
40
|
+
function get_config_instance(
|
|
41
|
+
config_path: string,
|
|
42
|
+
logger: ReturnType<typeof create_app_logger>,
|
|
43
|
+
): HazoConfig | null {
|
|
44
|
+
const cached = _config_instance_cache.get(config_path);
|
|
45
|
+
if (cached) return cached;
|
|
46
|
+
if (_config_failed_paths.has(config_path)) return null;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const instance = new HazoConfig({ filePath: config_path });
|
|
50
|
+
_config_instance_cache.set(config_path, instance);
|
|
51
|
+
return instance;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
_config_failed_paths.add(config_path);
|
|
54
|
+
const error_message = error instanceof Error ? error.message : "Unknown error";
|
|
55
|
+
logger.warn("config_loader_construct_failed", {
|
|
56
|
+
filename: "config_loader.server.ts",
|
|
57
|
+
line_number: 0,
|
|
58
|
+
config_path,
|
|
59
|
+
error: error_message,
|
|
60
|
+
});
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
27
65
|
/**
|
|
28
66
|
* Reads a section from the config file
|
|
29
67
|
* @param section_name - Name of the section to read (e.g., "hazo_auth__register_layout")
|
|
@@ -41,10 +79,10 @@ export function read_config_section(
|
|
|
41
79
|
return undefined;
|
|
42
80
|
}
|
|
43
81
|
|
|
82
|
+
const hazo_config = get_config_instance(config_path, logger);
|
|
83
|
+
if (!hazo_config) return undefined;
|
|
84
|
+
|
|
44
85
|
try {
|
|
45
|
-
const hazo_config = new HazoConfig({
|
|
46
|
-
filePath: config_path,
|
|
47
|
-
});
|
|
48
86
|
return hazo_config.getSection(section_name);
|
|
49
87
|
} catch (error) {
|
|
50
88
|
const error_message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// file_description: Zod-validated config loader for hazo_auth core settings.
|
|
2
|
-
// Covers server-critical sections ([hazo_auth__tokens], [hazo_auth__cookies], [hazo_auth__rate_limit], [
|
|
2
|
+
// Covers server-critical sections ([hazo_auth__tokens], [hazo_auth__cookies], [hazo_auth__rate_limit], [log_overrides]).
|
|
3
3
|
// UI sections (login_layout, register_layout, etc.) are still handled by config_loader.server.ts.
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { loadConfig } from 'hazo_core';
|