hazo_auth 5.1.17 → 5.1.19

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 CHANGED
@@ -453,11 +453,8 @@ Run the following SQL scripts in your PostgreSQL database:
453
453
  -- Enum type for profile picture source
454
454
  CREATE TYPE hazo_enum_profile_source_enum AS ENUM ('gravatar', 'custom', 'predefined');
455
455
 
456
- -- Scope types enum (for HRBAC)
457
- CREATE TYPE hazo_enum_scope_types AS ENUM (
458
- 'hazo_scopes_l1', 'hazo_scopes_l2', 'hazo_scopes_l3',
459
- 'hazo_scopes_l4', 'hazo_scopes_l5', 'hazo_scopes_l6', 'hazo_scopes_l7'
460
- );
456
+ -- Note: hazo_enum_scope_types was removed in v5.0
457
+ -- The unified hazo_scopes table uses a TEXT "level" column instead
461
458
  ```
462
459
 
463
460
  #### 2. Create the Organization Table (Multi-Tenancy)
@@ -612,10 +609,7 @@ For convenience, here's the complete SQL script to create all tables at once:
612
609
 
613
610
  -- 1. Create enum types
614
611
  CREATE TYPE hazo_enum_profile_source_enum AS ENUM ('gravatar', 'custom', 'predefined');
615
- CREATE TYPE hazo_enum_scope_types AS ENUM (
616
- 'hazo_scopes_l1', 'hazo_scopes_l2', 'hazo_scopes_l3',
617
- 'hazo_scopes_l4', 'hazo_scopes_l5', 'hazo_scopes_l6', 'hazo_scopes_l7'
618
- );
612
+ -- Note: hazo_enum_scope_types was removed in v5.0 (uses unified hazo_scopes table)
619
613
 
620
614
  -- 2. Create organization table (multi-tenancy)
621
615
  CREATE TABLE hazo_org (
@@ -1932,23 +1926,25 @@ enable_hrbac = true
1932
1926
  scope_cache_ttl_minutes = 15
1933
1927
  scope_cache_max_entries = 5000
1934
1928
 
1935
- # Optional: customize default labels for each scope level
1936
- default_label_l1 = Company
1937
- default_label_l2 = Division
1938
- default_label_l3 = Department
1939
- default_label_l4 = Team
1940
- default_label_l5 = Project
1941
- default_label_l6 = Sub-project
1942
- default_label_l7 = Task
1929
+ # Note: In v5.0+, scope levels are stored as the "level" column in hazo_scopes
1930
+ # Examples: "HQ", "Division", "Department", "Team", etc.
1931
+ # The level field is a descriptive string, not a fixed L1-L7 hierarchy
1943
1932
  ```
1944
1933
 
1945
1934
  ### Database Setup
1946
1935
 
1947
- HRBAC requires additional database tables. See `SETUP_CHECKLIST.md` for full PostgreSQL and SQLite scripts, including:
1948
- - `hazo_scopes_l1` through `hazo_scopes_l7` - Scope tables with parent_scope_id references
1949
- - `hazo_user_scopes` - User-scope assignments
1950
- - `hazo_scope_labels` - Custom labels per organization
1951
- - `hazo_enum_scope_types` - Enum type for scope validation
1936
+ HRBAC requires additional database tables. Run the scope consolidation migration:
1937
+
1938
+ ```bash
1939
+ npm run migrate migrations/009_scope_consolidation.sql
1940
+ ```
1941
+
1942
+ This creates:
1943
+ - `hazo_scopes` - Unified scope hierarchy with branding support
1944
+ - `hazo_user_scopes` - User-scope-role assignments
1945
+ - `hazo_invitations` - User invitation flow
1946
+
1947
+ See `SETUP_CHECKLIST.md` for full PostgreSQL and SQLite scripts.
1952
1948
 
1953
1949
  ### Using hazo_get_auth with Scope Options
1954
1950
 
@@ -1962,8 +1958,7 @@ export async function GET(request: NextRequest) {
1962
1958
  try {
1963
1959
  const authResult = await hazo_get_auth(request, {
1964
1960
  required_permissions: ["view_reports"],
1965
- scope_type: "hazo_scopes_l3", // Check access to Level 3 scope
1966
- scope_seq: "L3_001", // Scope identifier (or use scope_id for UUID)
1961
+ scope_id: "uuid-of-scope", // Check access to specific scope
1967
1962
  strict: true, // Throws ScopeAccessError if denied
1968
1963
  });
1969
1964
 
@@ -304,16 +304,20 @@ cat << 'EOF' | sqlite3 data/hazo_auth.sqlite
304
304
  CREATE TABLE IF NOT EXISTS hazo_users (
305
305
  id TEXT PRIMARY KEY,
306
306
  email_address TEXT NOT NULL UNIQUE,
307
- password_hash TEXT NOT NULL,
307
+ password_hash TEXT,
308
308
  name TEXT,
309
309
  email_verified INTEGER NOT NULL DEFAULT 0,
310
- is_active INTEGER NOT NULL DEFAULT 1,
310
+ status TEXT DEFAULT 'ACTIVE' CHECK(status IN ('PENDING', 'ACTIVE', 'BLOCKED')),
311
311
  login_attempts INTEGER NOT NULL DEFAULT 0,
312
312
  last_logon TEXT,
313
313
  profile_picture_url TEXT,
314
- profile_source TEXT,
314
+ profile_source TEXT CHECK(profile_source IN ('gravatar', 'custom', 'predefined')),
315
315
  mfa_secret TEXT,
316
316
  url_on_logon TEXT,
317
+ google_id TEXT UNIQUE,
318
+ auth_providers TEXT DEFAULT 'email',
319
+ user_type TEXT,
320
+ app_user_data TEXT,
317
321
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
318
322
  changed_at TEXT NOT NULL DEFAULT (datetime('now'))
319
323
  );
@@ -426,7 +430,7 @@ CREATE TABLE hazo_users (
426
430
  password_hash TEXT, -- NULL for OAuth-only users
427
431
  name TEXT,
428
432
  email_verified BOOLEAN NOT NULL DEFAULT FALSE,
429
- is_active BOOLEAN NOT NULL DEFAULT TRUE,
433
+ status TEXT NOT NULL DEFAULT 'ACTIVE', -- 'PENDING', 'ACTIVE', or 'BLOCKED'
430
434
  login_attempts INTEGER NOT NULL DEFAULT 0,
431
435
  last_logon TIMESTAMP WITH TIME ZONE,
432
436
  profile_picture_url TEXT,
@@ -434,6 +438,7 @@ CREATE TABLE hazo_users (
434
438
  mfa_secret TEXT,
435
439
  url_on_logon TEXT,
436
440
  user_type TEXT, -- Optional user categorization
441
+ app_user_data TEXT, -- Custom JSON data for consuming apps
437
442
  google_id TEXT UNIQUE, -- Google OAuth ID
438
443
  auth_providers TEXT DEFAULT 'email', -- 'email', 'google', or 'email,google'
439
444
  org_id UUID REFERENCES hazo_org(id) ON DELETE SET NULL,
@@ -442,6 +447,7 @@ CREATE TABLE hazo_users (
442
447
  changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
443
448
  );
444
449
  CREATE INDEX idx_hazo_users_email ON hazo_users(email_address);
450
+ CREATE INDEX idx_hazo_users_status ON hazo_users(status);
445
451
  CREATE INDEX idx_hazo_users_user_type ON hazo_users(user_type);
446
452
  CREATE UNIQUE INDEX idx_hazo_users_google_id ON hazo_users(google_id);
447
453
  CREATE INDEX idx_hazo_users_org_id ON hazo_users(org_id);
@@ -581,7 +587,7 @@ GRANT USAGE ON TYPE hazo_enum_profile_source_enum TO anon, authenticated;
581
587
  - [ ] `hazo_enum_chat_type`
582
588
  - [ ] All core tables exist:
583
589
  - [ ] `hazo_org` (multi-tenancy - must be created before hazo_users)
584
- - [ ] `hazo_users` (with google_id, auth_providers, org_id, root_org_id, user_type fields)
590
+ - [ ] `hazo_users` (with status, google_id, auth_providers, app_user_data, org_id, root_org_id, user_type fields)
585
591
  - [ ] `hazo_refresh_tokens`
586
592
  - [ ] `hazo_permissions`
587
593
  - [ ] `hazo_roles`
@@ -1322,14 +1328,9 @@ enable_hrbac = true
1322
1328
  scope_cache_ttl_minutes = 15
1323
1329
  scope_cache_max_entries = 5000
1324
1330
 
1325
- # Optional: customize default labels for each scope level
1326
- default_label_l1 = Company
1327
- default_label_l2 = Division
1328
- default_label_l3 = Department
1329
- default_label_l4 = Team
1330
- default_label_l5 = Project
1331
- default_label_l6 = Sub-project
1332
- default_label_l7 = Task
1331
+ # Note: In v5.0+, scope levels are stored as the "level" column in hazo_scopes
1332
+ # Examples: "HQ", "Division", "Department", "Team", etc.
1333
+ # The level field is a descriptive string, not a fixed L1-L7 hierarchy
1333
1334
  ```
1334
1335
 
1335
1336
  ### Step 7.2: Add HRBAC Permissions
@@ -1343,185 +1344,89 @@ application_permission_list_defaults = admin_user_management,admin_role_manageme
1343
1344
 
1344
1345
  ### Step 7.3: Create HRBAC Database Tables
1345
1346
 
1347
+ **Recommended:** Run the scope consolidation migration which creates all required tables:
1348
+
1349
+ ```bash
1350
+ npm run migrate migrations/009_scope_consolidation.sql
1351
+ ```
1352
+
1353
+ If you prefer manual setup, use the scripts below:
1354
+
1346
1355
  #### PostgreSQL
1347
1356
 
1348
1357
  ```sql
1349
1358
  -- =============================================
1350
1359
  -- HRBAC Database Setup Script (PostgreSQL)
1360
+ -- v5.0+ Unified Scope Model
1351
1361
  -- =============================================
1352
1362
 
1353
- -- 1. Create scope types enum
1354
- CREATE TYPE hazo_enum_scope_types AS ENUM (
1355
- 'hazo_scopes_l1',
1356
- 'hazo_scopes_l2',
1357
- 'hazo_scopes_l3',
1358
- 'hazo_scopes_l4',
1359
- 'hazo_scopes_l5',
1360
- 'hazo_scopes_l6',
1361
- 'hazo_scopes_l7'
1362
- );
1363
-
1364
- -- 2. Create sequence generator function for scope IDs
1365
- CREATE OR REPLACE FUNCTION hazo_scope_id_generator(table_name TEXT)
1366
- RETURNS TEXT AS $$
1367
- DECLARE
1368
- prefix TEXT;
1369
- next_num INTEGER;
1370
- result TEXT;
1371
- BEGIN
1372
- -- Extract level number from table name (e.g., 'hazo_scopes_l3' -> '3')
1373
- prefix := 'L' || SUBSTRING(table_name FROM 'hazo_scopes_l([0-9]+)');
1374
-
1375
- -- Get the next sequence number
1376
- EXECUTE format(
1377
- 'SELECT COALESCE(MAX(CAST(SUBSTRING(seq FROM ''L[0-9]+_([0-9]+)'') AS INTEGER)), 0) + 1 FROM %I',
1378
- table_name
1379
- ) INTO next_num;
1380
-
1381
- -- Format as L{level}_{padded_number}
1382
- result := prefix || '_' || LPAD(next_num::TEXT, 3, '0');
1383
-
1384
- RETURN result;
1385
- END;
1386
- $$ LANGUAGE plpgsql;
1387
-
1388
- -- 3. Create scope tables (L1 through L7)
1389
-
1390
- -- Level 1 (top level - no parent)
1391
- CREATE TABLE hazo_scopes_l1 (
1363
+ -- 1. Create unified hazo_scopes table (with branding columns)
1364
+ CREATE TABLE IF NOT EXISTS hazo_scopes (
1392
1365
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1393
- seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l1'),
1394
- org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
1395
- root_org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
1366
+ parent_id UUID REFERENCES hazo_scopes(id) ON DELETE CASCADE,
1396
1367
  name TEXT NOT NULL,
1368
+ level TEXT NOT NULL,
1369
+ logo_url TEXT,
1370
+ primary_color TEXT,
1371
+ secondary_color TEXT,
1372
+ tagline TEXT,
1373
+ slug TEXT,
1397
1374
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1398
1375
  changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
1399
1376
  );
1400
- CREATE INDEX idx_hazo_scopes_l1_org_id ON hazo_scopes_l1(org_id);
1401
- CREATE INDEX idx_hazo_scopes_l1_root_org_id ON hazo_scopes_l1(root_org_id);
1402
- CREATE INDEX idx_hazo_scopes_l1_seq ON hazo_scopes_l1(seq);
1403
1377
 
1404
- -- Level 2 (parent: L1)
1405
- CREATE TABLE hazo_scopes_l2 (
1406
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1407
- seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l2'),
1408
- org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
1409
- root_org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
1410
- name TEXT NOT NULL,
1411
- parent_scope_id UUID REFERENCES hazo_scopes_l1(id) ON DELETE CASCADE,
1412
- created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1413
- changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
1414
- );
1415
- CREATE INDEX idx_hazo_scopes_l2_org_id ON hazo_scopes_l2(org_id);
1416
- CREATE INDEX idx_hazo_scopes_l2_root_org_id ON hazo_scopes_l2(root_org_id);
1417
- CREATE INDEX idx_hazo_scopes_l2_seq ON hazo_scopes_l2(seq);
1418
- CREATE INDEX idx_hazo_scopes_l2_parent ON hazo_scopes_l2(parent_scope_id);
1378
+ -- 2. Create indexes for hierarchy traversal
1379
+ CREATE INDEX IF NOT EXISTS idx_hazo_scopes_parent ON hazo_scopes(parent_id);
1380
+ CREATE INDEX IF NOT EXISTS idx_hazo_scopes_level ON hazo_scopes(level);
1381
+ CREATE INDEX IF NOT EXISTS idx_hazo_scopes_slug ON hazo_scopes(slug);
1419
1382
 
1420
- -- Level 3 (parent: L2)
1421
- CREATE TABLE hazo_scopes_l3 (
1422
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1423
- seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l3'),
1424
- org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
1425
- root_org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
1426
- name TEXT NOT NULL,
1427
- parent_scope_id UUID REFERENCES hazo_scopes_l2(id) ON DELETE CASCADE,
1428
- created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1429
- changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
1430
- );
1431
- CREATE INDEX idx_hazo_scopes_l3_org_id ON hazo_scopes_l3(org_id);
1432
- CREATE INDEX idx_hazo_scopes_l3_root_org_id ON hazo_scopes_l3(root_org_id);
1433
- CREATE INDEX idx_hazo_scopes_l3_seq ON hazo_scopes_l3(seq);
1434
- CREATE INDEX idx_hazo_scopes_l3_parent ON hazo_scopes_l3(parent_scope_id);
1383
+ -- 3. Create system scopes
1384
+ INSERT INTO hazo_scopes (id, parent_id, name, level, created_at, changed_at)
1385
+ VALUES ('00000000-0000-0000-0000-000000000000', NULL, 'Super Admin', 'system', NOW(), NOW())
1386
+ ON CONFLICT (id) DO NOTHING;
1435
1387
 
1436
- -- Level 4 (parent: L3)
1437
- CREATE TABLE hazo_scopes_l4 (
1438
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1439
- seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l4'),
1440
- org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
1441
- root_org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
1442
- name TEXT NOT NULL,
1443
- parent_scope_id UUID REFERENCES hazo_scopes_l3(id) ON DELETE CASCADE,
1444
- created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1445
- changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
1446
- );
1447
- CREATE INDEX idx_hazo_scopes_l4_org_id ON hazo_scopes_l4(org_id);
1448
- CREATE INDEX idx_hazo_scopes_l4_root_org_id ON hazo_scopes_l4(root_org_id);
1449
- CREATE INDEX idx_hazo_scopes_l4_seq ON hazo_scopes_l4(seq);
1450
- CREATE INDEX idx_hazo_scopes_l4_parent ON hazo_scopes_l4(parent_scope_id);
1388
+ INSERT INTO hazo_scopes (id, parent_id, name, level, created_at, changed_at)
1389
+ VALUES ('00000000-0000-0000-0000-000000000001', NULL, 'System', 'default', NOW(), NOW())
1390
+ ON CONFLICT (id) DO NOTHING;
1451
1391
 
1452
- -- Level 5 (parent: L4)
1453
- CREATE TABLE hazo_scopes_l5 (
1454
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1455
- seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l5'),
1456
- org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
1457
- root_org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
1458
- name TEXT NOT NULL,
1459
- parent_scope_id UUID REFERENCES hazo_scopes_l4(id) ON DELETE CASCADE,
1392
+ -- 4. Create user scopes junction table (membership-based multi-tenancy)
1393
+ CREATE TABLE IF NOT EXISTS hazo_user_scopes (
1394
+ user_id UUID NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,
1395
+ scope_id UUID NOT NULL REFERENCES hazo_scopes(id) ON DELETE CASCADE,
1396
+ root_scope_id UUID NOT NULL REFERENCES hazo_scopes(id) ON DELETE CASCADE,
1397
+ role_id UUID NOT NULL REFERENCES hazo_roles(id) ON DELETE CASCADE,
1398
+ status TEXT DEFAULT 'ACTIVE' CHECK (status IN ('INVITED', 'ACTIVE', 'SUSPENDED', 'DEPARTED')),
1460
1399
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1461
- changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
1400
+ changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1401
+ PRIMARY KEY (user_id, scope_id)
1462
1402
  );
1463
- CREATE INDEX idx_hazo_scopes_l5_org_id ON hazo_scopes_l5(org_id);
1464
- CREATE INDEX idx_hazo_scopes_l5_root_org_id ON hazo_scopes_l5(root_org_id);
1465
- CREATE INDEX idx_hazo_scopes_l5_seq ON hazo_scopes_l5(seq);
1466
- CREATE INDEX idx_hazo_scopes_l5_parent ON hazo_scopes_l5(parent_scope_id);
1467
1403
 
1468
- -- Level 6 (parent: L5)
1469
- CREATE TABLE hazo_scopes_l6 (
1470
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1471
- seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l6'),
1472
- org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
1473
- root_org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
1474
- name TEXT NOT NULL,
1475
- parent_scope_id UUID REFERENCES hazo_scopes_l5(id) ON DELETE CASCADE,
1476
- created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1477
- changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
1478
- );
1479
- CREATE INDEX idx_hazo_scopes_l6_org_id ON hazo_scopes_l6(org_id);
1480
- CREATE INDEX idx_hazo_scopes_l6_root_org_id ON hazo_scopes_l6(root_org_id);
1481
- CREATE INDEX idx_hazo_scopes_l6_seq ON hazo_scopes_l6(seq);
1482
- CREATE INDEX idx_hazo_scopes_l6_parent ON hazo_scopes_l6(parent_scope_id);
1404
+ CREATE INDEX IF NOT EXISTS idx_hazo_user_scopes_scope ON hazo_user_scopes(scope_id);
1405
+ CREATE INDEX IF NOT EXISTS idx_hazo_user_scopes_root ON hazo_user_scopes(root_scope_id);
1406
+ CREATE INDEX IF NOT EXISTS idx_hazo_user_scopes_role ON hazo_user_scopes(role_id);
1483
1407
 
1484
- -- Level 7 (parent: L6)
1485
- CREATE TABLE hazo_scopes_l7 (
1408
+ -- 5. Create invitations table (for onboarding new users)
1409
+ CREATE TABLE IF NOT EXISTS hazo_invitations (
1486
1410
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1487
- seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l7'),
1488
- org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
1489
- root_org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
1490
- name TEXT NOT NULL,
1491
- parent_scope_id UUID REFERENCES hazo_scopes_l6(id) ON DELETE CASCADE,
1411
+ email_address TEXT NOT NULL,
1412
+ scope_id UUID NOT NULL REFERENCES hazo_scopes(id) ON DELETE CASCADE,
1413
+ role_id UUID NOT NULL REFERENCES hazo_roles(id) ON DELETE CASCADE,
1414
+ status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING', 'ACCEPTED', 'EXPIRED', 'REVOKED')),
1415
+ invited_by UUID REFERENCES hazo_users(id) ON DELETE SET NULL,
1416
+ expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
1417
+ accepted_at TIMESTAMP WITH TIME ZONE,
1492
1418
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1493
1419
  changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
1494
1420
  );
1495
- CREATE INDEX idx_hazo_scopes_l7_org_id ON hazo_scopes_l7(org_id);
1496
- CREATE INDEX idx_hazo_scopes_l7_root_org_id ON hazo_scopes_l7(root_org_id);
1497
- CREATE INDEX idx_hazo_scopes_l7_seq ON hazo_scopes_l7(seq);
1498
- CREATE INDEX idx_hazo_scopes_l7_parent ON hazo_scopes_l7(parent_scope_id);
1499
1421
 
1500
- -- 4. Create user scopes junction table
1501
- CREATE TABLE hazo_user_scopes (
1502
- user_id UUID NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,
1503
- scope_id UUID NOT NULL,
1504
- scope_seq TEXT NOT NULL,
1505
- scope_type hazo_enum_scope_types NOT NULL,
1506
- created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1507
- changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1508
- PRIMARY KEY (user_id, scope_type, scope_id)
1509
- );
1510
- CREATE INDEX idx_hazo_user_scopes_user_id ON hazo_user_scopes(user_id);
1511
- CREATE INDEX idx_hazo_user_scopes_scope_id ON hazo_user_scopes(scope_id);
1512
- CREATE INDEX idx_hazo_user_scopes_scope_type ON hazo_user_scopes(scope_type);
1422
+ CREATE INDEX IF NOT EXISTS idx_hazo_invitations_email ON hazo_invitations(email_address);
1423
+ CREATE INDEX IF NOT EXISTS idx_hazo_invitations_scope ON hazo_invitations(scope_id);
1424
+ CREATE INDEX IF NOT EXISTS idx_hazo_invitations_status ON hazo_invitations(status);
1513
1425
 
1514
- -- 5. Create scope labels table
1515
- CREATE TABLE hazo_scope_labels (
1516
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1517
- org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
1518
- scope_type hazo_enum_scope_types NOT NULL,
1519
- label TEXT NOT NULL,
1520
- created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1521
- changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1522
- UNIQUE(org_id, scope_type)
1523
- );
1524
- CREATE INDEX idx_hazo_scope_labels_org_id ON hazo_scope_labels(org_id);
1426
+ -- 6. Create firm_admin role (for firm creators)
1427
+ INSERT INTO hazo_roles (id, role_name, created_at, changed_at)
1428
+ VALUES (gen_random_uuid(), 'firm_admin', NOW(), NOW())
1429
+ ON CONFLICT DO NOTHING;
1525
1430
  ```
1526
1431
 
1527
1432
  #### PostgreSQL Grant Scripts
@@ -1530,30 +1435,14 @@ After creating the tables, grant appropriate permissions:
1530
1435
 
1531
1436
  ```sql
1532
1437
  -- Grant to your admin user (replace 'your_admin_user' with actual username)
1533
- GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l1 TO your_admin_user;
1534
- GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l2 TO your_admin_user;
1535
- GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l3 TO your_admin_user;
1536
- GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l4 TO your_admin_user;
1537
- GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l5 TO your_admin_user;
1538
- GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l6 TO your_admin_user;
1539
- GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l7 TO your_admin_user;
1438
+ GRANT ALL PRIVILEGES ON TABLE hazo_scopes TO your_admin_user;
1540
1439
  GRANT ALL PRIVILEGES ON TABLE hazo_user_scopes TO your_admin_user;
1541
- GRANT ALL PRIVILEGES ON TABLE hazo_scope_labels TO your_admin_user;
1542
- GRANT USAGE ON TYPE hazo_enum_scope_types TO your_admin_user;
1543
- GRANT EXECUTE ON FUNCTION hazo_scope_id_generator(TEXT) TO your_admin_user;
1440
+ GRANT ALL PRIVILEGES ON TABLE hazo_invitations TO your_admin_user;
1544
1441
 
1545
1442
  -- For PostgREST authenticated role
1546
- GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l1 TO authenticated;
1547
- GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l2 TO authenticated;
1548
- GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l3 TO authenticated;
1549
- GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l4 TO authenticated;
1550
- GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l5 TO authenticated;
1551
- GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l6 TO authenticated;
1552
- GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l7 TO authenticated;
1443
+ GRANT ALL PRIVILEGES ON TABLE hazo_scopes TO authenticated;
1553
1444
  GRANT ALL PRIVILEGES ON TABLE hazo_user_scopes TO authenticated;
1554
- GRANT ALL PRIVILEGES ON TABLE hazo_scope_labels TO authenticated;
1555
- GRANT USAGE ON TYPE hazo_enum_scope_types TO authenticated;
1556
- GRANT EXECUTE ON FUNCTION hazo_scope_id_generator(TEXT) TO authenticated;
1445
+ GRANT ALL PRIVILEGES ON TABLE hazo_invitations TO authenticated;
1557
1446
  ```
1558
1447
 
1559
1448
  #### SQLite
@@ -1561,100 +1450,69 @@ GRANT EXECUTE ON FUNCTION hazo_scope_id_generator(TEXT) TO authenticated;
1561
1450
  ```sql
1562
1451
  -- =============================================
1563
1452
  -- HRBAC Database Setup Script (SQLite)
1453
+ -- v5.0+ Unified Scope Model
1564
1454
  -- =============================================
1565
1455
 
1566
- -- Scope tables (L1 through L7)
1567
-
1568
- CREATE TABLE IF NOT EXISTS hazo_scopes_l1 (
1569
- id TEXT PRIMARY KEY,
1570
- seq TEXT NOT NULL,
1571
- org TEXT NOT NULL,
1572
- name TEXT NOT NULL,
1573
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
1574
- changed_at TEXT NOT NULL DEFAULT (datetime('now'))
1575
- );
1576
-
1577
- CREATE TABLE IF NOT EXISTS hazo_scopes_l2 (
1456
+ -- 1. Create unified hazo_scopes table (with branding columns)
1457
+ CREATE TABLE IF NOT EXISTS hazo_scopes (
1578
1458
  id TEXT PRIMARY KEY,
1579
- seq TEXT NOT NULL,
1580
- org TEXT NOT NULL,
1459
+ parent_id TEXT REFERENCES hazo_scopes(id) ON DELETE CASCADE,
1581
1460
  name TEXT NOT NULL,
1582
- parent_scope_id TEXT REFERENCES hazo_scopes_l1(id) ON DELETE CASCADE,
1461
+ level TEXT NOT NULL,
1462
+ logo_url TEXT,
1463
+ primary_color TEXT,
1464
+ secondary_color TEXT,
1465
+ tagline TEXT,
1466
+ slug TEXT,
1583
1467
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
1584
1468
  changed_at TEXT NOT NULL DEFAULT (datetime('now'))
1585
1469
  );
1586
1470
 
1587
- CREATE TABLE IF NOT EXISTS hazo_scopes_l3 (
1588
- id TEXT PRIMARY KEY,
1589
- seq TEXT NOT NULL,
1590
- org TEXT NOT NULL,
1591
- name TEXT NOT NULL,
1592
- parent_scope_id TEXT REFERENCES hazo_scopes_l2(id) ON DELETE CASCADE,
1593
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
1594
- changed_at TEXT NOT NULL DEFAULT (datetime('now'))
1595
- );
1471
+ -- 2. Create indexes for hierarchy traversal
1472
+ CREATE INDEX IF NOT EXISTS idx_hazo_scopes_parent ON hazo_scopes(parent_id);
1473
+ CREATE INDEX IF NOT EXISTS idx_hazo_scopes_level ON hazo_scopes(level);
1474
+ CREATE INDEX IF NOT EXISTS idx_hazo_scopes_slug ON hazo_scopes(slug);
1596
1475
 
1597
- CREATE TABLE IF NOT EXISTS hazo_scopes_l4 (
1598
- id TEXT PRIMARY KEY,
1599
- seq TEXT NOT NULL,
1600
- org TEXT NOT NULL,
1601
- name TEXT NOT NULL,
1602
- parent_scope_id TEXT REFERENCES hazo_scopes_l3(id) ON DELETE CASCADE,
1603
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
1604
- changed_at TEXT NOT NULL DEFAULT (datetime('now'))
1605
- );
1476
+ -- 3. Create system scopes
1477
+ INSERT OR IGNORE INTO hazo_scopes (id, parent_id, name, level, created_at, changed_at)
1478
+ VALUES ('00000000-0000-0000-0000-000000000000', NULL, 'Super Admin', 'system', datetime('now'), datetime('now'));
1606
1479
 
1607
- CREATE TABLE IF NOT EXISTS hazo_scopes_l5 (
1608
- id TEXT PRIMARY KEY,
1609
- seq TEXT NOT NULL,
1610
- org TEXT NOT NULL,
1611
- name TEXT NOT NULL,
1612
- parent_scope_id TEXT REFERENCES hazo_scopes_l4(id) ON DELETE CASCADE,
1613
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
1614
- changed_at TEXT NOT NULL DEFAULT (datetime('now'))
1615
- );
1480
+ INSERT OR IGNORE INTO hazo_scopes (id, parent_id, name, level, created_at, changed_at)
1481
+ VALUES ('00000000-0000-0000-0000-000000000001', NULL, 'System', 'default', datetime('now'), datetime('now'));
1616
1482
 
1617
- CREATE TABLE IF NOT EXISTS hazo_scopes_l6 (
1618
- id TEXT PRIMARY KEY,
1619
- seq TEXT NOT NULL,
1620
- org TEXT NOT NULL,
1621
- name TEXT NOT NULL,
1622
- parent_scope_id TEXT REFERENCES hazo_scopes_l5(id) ON DELETE CASCADE,
1623
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
1624
- changed_at TEXT NOT NULL DEFAULT (datetime('now'))
1625
- );
1626
-
1627
- CREATE TABLE IF NOT EXISTS hazo_scopes_l7 (
1628
- id TEXT PRIMARY KEY,
1629
- seq TEXT NOT NULL,
1630
- org TEXT NOT NULL,
1631
- name TEXT NOT NULL,
1632
- parent_scope_id TEXT REFERENCES hazo_scopes_l6(id) ON DELETE CASCADE,
1633
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
1634
- changed_at TEXT NOT NULL DEFAULT (datetime('now'))
1635
- );
1636
-
1637
- -- User scopes junction table
1483
+ -- 4. Create user scopes junction table (membership-based multi-tenancy)
1638
1484
  CREATE TABLE IF NOT EXISTS hazo_user_scopes (
1639
1485
  user_id TEXT NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,
1640
- scope_id TEXT NOT NULL,
1641
- scope_seq TEXT NOT NULL,
1642
- scope_type TEXT NOT NULL CHECK(scope_type IN ('hazo_scopes_l1','hazo_scopes_l2','hazo_scopes_l3','hazo_scopes_l4','hazo_scopes_l5','hazo_scopes_l6','hazo_scopes_l7')),
1486
+ scope_id TEXT NOT NULL REFERENCES hazo_scopes(id) ON DELETE CASCADE,
1487
+ root_scope_id TEXT NOT NULL REFERENCES hazo_scopes(id) ON DELETE CASCADE,
1488
+ role_id TEXT NOT NULL REFERENCES hazo_roles(id) ON DELETE CASCADE,
1489
+ status TEXT DEFAULT 'ACTIVE' CHECK (status IN ('INVITED', 'ACTIVE', 'SUSPENDED', 'DEPARTED')),
1643
1490
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
1644
1491
  changed_at TEXT NOT NULL DEFAULT (datetime('now')),
1645
- PRIMARY KEY (user_id, scope_type, scope_id)
1492
+ PRIMARY KEY (user_id, scope_id)
1646
1493
  );
1647
1494
 
1648
- -- Scope labels table
1649
- CREATE TABLE IF NOT EXISTS hazo_scope_labels (
1495
+ CREATE INDEX IF NOT EXISTS idx_hazo_user_scopes_scope ON hazo_user_scopes(scope_id);
1496
+ CREATE INDEX IF NOT EXISTS idx_hazo_user_scopes_root ON hazo_user_scopes(root_scope_id);
1497
+ CREATE INDEX IF NOT EXISTS idx_hazo_user_scopes_role ON hazo_user_scopes(role_id);
1498
+
1499
+ -- 5. Create invitations table (for onboarding new users)
1500
+ CREATE TABLE IF NOT EXISTS hazo_invitations (
1650
1501
  id TEXT PRIMARY KEY,
1651
- org TEXT NOT NULL,
1652
- scope_type TEXT NOT NULL CHECK(scope_type IN ('hazo_scopes_l1','hazo_scopes_l2','hazo_scopes_l3','hazo_scopes_l4','hazo_scopes_l5','hazo_scopes_l6','hazo_scopes_l7')),
1653
- label TEXT NOT NULL,
1502
+ email_address TEXT NOT NULL,
1503
+ scope_id TEXT NOT NULL REFERENCES hazo_scopes(id) ON DELETE CASCADE,
1504
+ role_id TEXT NOT NULL REFERENCES hazo_roles(id) ON DELETE CASCADE,
1505
+ status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING', 'ACCEPTED', 'EXPIRED', 'REVOKED')),
1506
+ invited_by TEXT REFERENCES hazo_users(id) ON DELETE SET NULL,
1507
+ expires_at TEXT NOT NULL,
1508
+ accepted_at TEXT,
1654
1509
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
1655
- changed_at TEXT NOT NULL DEFAULT (datetime('now')),
1656
- UNIQUE(org, scope_type)
1510
+ changed_at TEXT NOT NULL DEFAULT (datetime('now'))
1657
1511
  );
1512
+
1513
+ CREATE INDEX IF NOT EXISTS idx_hazo_invitations_email ON hazo_invitations(email_address);
1514
+ CREATE INDEX IF NOT EXISTS idx_hazo_invitations_scope ON hazo_invitations(scope_id);
1515
+ CREATE INDEX IF NOT EXISTS idx_hazo_invitations_status ON hazo_invitations(status);
1658
1516
  ```
1659
1517
 
1660
1518
  ### Step 7.4: Verify HRBAC Tables
@@ -1662,14 +1520,13 @@ CREATE TABLE IF NOT EXISTS hazo_scope_labels (
1662
1520
  #### PostgreSQL
1663
1521
  ```sql
1664
1522
  SELECT table_name FROM information_schema.tables
1665
- WHERE table_name LIKE 'hazo_scopes_%'
1666
- OR table_name IN ('hazo_user_scopes', 'hazo_scope_labels');
1667
- -- Expected: 9 tables (7 scope tables + user_scopes + scope_labels)
1523
+ WHERE table_name IN ('hazo_scopes', 'hazo_user_scopes', 'hazo_invitations');
1524
+ -- Expected: 3 tables (hazo_scopes, hazo_user_scopes, hazo_invitations)
1668
1525
  ```
1669
1526
 
1670
1527
  #### SQLite
1671
1528
  ```bash
1672
- sqlite3 data/hazo_auth.sqlite ".tables" | grep -E "hazo_scopes|hazo_user_scopes|hazo_scope_labels"
1529
+ sqlite3 data/hazo_auth.sqlite ".tables" | grep -E "hazo_scopes|hazo_user_scopes|hazo_invitations"
1673
1530
  ```
1674
1531
 
1675
1532
  ### Step 7.5: Add Slug Column to hazo_scopes (Optional - for Tenant Auth)
@@ -1710,13 +1567,14 @@ CREATE INDEX IF NOT EXISTS idx_hazo_scopes_slug ON hazo_scopes(slug);
1710
1567
  **HRBAC Checklist:**
1711
1568
  - [ ] `enable_hrbac = true` in config
1712
1569
  - [ ] HRBAC permissions added to defaults
1713
- - [ ] All 9 HRBAC tables created:
1714
- - [ ] `hazo_scopes_l1` through `hazo_scopes_l7` (with org_id, root_org_id FKs)
1715
- - [ ] `hazo_user_scopes` (junction table)
1716
- - [ ] `hazo_scope_labels` (custom labels per org)
1717
- - [ ] Scope ID generator function created (PostgreSQL)
1570
+ - [ ] All HRBAC tables created:
1571
+ - [ ] `hazo_scopes` (unified scope hierarchy with branding)
1572
+ - [ ] `hazo_user_scopes` (user-scope-role assignments)
1573
+ - [ ] `hazo_invitations` (user invitation flow)
1574
+ - [ ] System scopes exist:
1575
+ - [ ] Super Admin scope (00000000-0000-0000-0000-000000000000)
1576
+ - [ ] Default System scope (00000000-0000-0000-0000-000000000001)
1718
1577
  - [ ] Grants applied (PostgreSQL)
1719
- - [ ] `slug` column added to hazo_scopes (optional, for tenant auth)
1720
1578
  - [ ] HRBAC tabs visible in User Management
1721
1579
  - [ ] Scope test page works
1722
1580
 
@@ -1897,6 +1755,50 @@ curl -H "Cookie: hazo_auth_session=YOUR_TOKEN; hazo_auth_scope_id=SCOPE_UUID" \
1897
1755
 
1898
1756
  ## Troubleshooting
1899
1757
 
1758
+ ### Issue: "User is inactive" or "auth_utility_fetch_user_failed" errors
1759
+
1760
+ **Symptoms:** Users can't log in, error logs show `auth_utility_fetch_user_failed` or "User is inactive" even for newly created users.
1761
+
1762
+ **Cause:** Your database has an `is_active` column instead of the required `status` column. The code expects `status TEXT` with value `'ACTIVE'`.
1763
+
1764
+ **Solutions:**
1765
+
1766
+ 1. **Run the status migration** (recommended):
1767
+ ```bash
1768
+ npm run migrate migrations/011_fix_status_case_to_uppercase.sql
1769
+ ```
1770
+
1771
+ 2. **Or manually add the status column:**
1772
+
1773
+ **SQLite:**
1774
+ ```sql
1775
+ -- Add status column
1776
+ ALTER TABLE hazo_users ADD COLUMN status TEXT DEFAULT 'ACTIVE';
1777
+
1778
+ -- Migrate is_active values to status
1779
+ UPDATE hazo_users SET status = CASE
1780
+ WHEN is_active = 1 OR is_active = TRUE THEN 'ACTIVE'
1781
+ ELSE 'BLOCKED'
1782
+ END WHERE status IS NULL;
1783
+ ```
1784
+
1785
+ **PostgreSQL:**
1786
+ ```sql
1787
+ -- Add status column
1788
+ ALTER TABLE hazo_users ADD COLUMN status TEXT NOT NULL DEFAULT 'ACTIVE';
1789
+
1790
+ -- Migrate is_active values to status
1791
+ UPDATE hazo_users SET status = CASE
1792
+ WHEN is_active = TRUE THEN 'ACTIVE'
1793
+ ELSE 'BLOCKED'
1794
+ END;
1795
+
1796
+ -- Create index
1797
+ CREATE INDEX IF NOT EXISTS idx_hazo_users_status ON hazo_users(status);
1798
+ ```
1799
+
1800
+ **Note:** The `status` column uses text values: `'PENDING'`, `'ACTIVE'`, or `'BLOCKED'`.
1801
+
1900
1802
  ### Issue: Email not sending
1901
1803
 
1902
1804
  **Symptoms:** Registration succeeds but no email received.
@@ -149,7 +149,10 @@ export async function handle_init_users(options: InitUsersOptions = {}): Promise
149
149
  const hazoConnect = get_hazo_connect_instance();
150
150
  const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
151
151
  const roles_service = createCrudService(hazoConnect, "hazo_roles");
152
- const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions");
152
+ const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions", {
153
+ primaryKeys: ["role_id", "permission_id"],
154
+ autoId: false,
155
+ });
153
156
  const users_service = createCrudService(hazoConnect, "hazo_users");
154
157
  // v5.x: Removed hazo_user_roles - roles are now assigned via hazo_user_scopes
155
158
  const scopes_service = createCrudService(hazoConnect, "hazo_scopes");
@@ -152,6 +152,7 @@ async function fetch_user_data_from_db(user_id: string): Promise<{
152
152
  const role_permissions_service = createCrudService(
153
153
  hazoConnect,
154
154
  "hazo_role_permissions",
155
+ { primaryKeys: ["role_id", "permission_id"], autoId: false },
155
156
  );
156
157
  const permissions_service = createCrudService(
157
158
  hazoConnect,
@@ -122,6 +122,7 @@ async function assign_owner_permissions(
122
122
  const role_permission_service = createCrudService(
123
123
  adapter,
124
124
  "hazo_role_permissions",
125
+ { primaryKeys: ["role_id", "permission_id"], autoId: false },
125
126
  );
126
127
 
127
128
  // Default permissions for firm owner
@@ -1 +1 @@
1
- {"version":3,"file":"init_users.d.ts","sourceRoot":"","sources":["../../src/cli/init_users.ts"],"names":[],"mappings":"AAuGA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAGF;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmRrF;AAGD;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAkC3C"}
1
+ {"version":3,"file":"init_users.d.ts","sourceRoot":"","sources":["../../src/cli/init_users.ts"],"names":[],"mappings":"AAuGA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAGF;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsRrF;AAGD;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAkC3C"}
@@ -107,7 +107,10 @@ export async function handle_init_users(options = {}) {
107
107
  const hazoConnect = get_hazo_connect_instance();
108
108
  const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
109
109
  const roles_service = createCrudService(hazoConnect, "hazo_roles");
110
- const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions");
110
+ const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions", {
111
+ primaryKeys: ["role_id", "permission_id"],
112
+ autoId: false,
113
+ });
111
114
  const users_service = createCrudService(hazoConnect, "hazo_users");
112
115
  // v5.x: Removed hazo_user_roles - roles are now assigned via hazo_user_scopes
113
116
  const scopes_service = createCrudService(hazoConnect, "hazo_scopes");
@@ -1 +1 @@
1
- {"version":3,"file":"hazo_get_auth.server.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/hazo_get_auth.server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK1C,OAAO,KAAK,EACV,cAAc,EAEd,eAAe,EAGhB,MAAM,cAAc,CAAC;AAsWtB;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,cAAc,CAAC,CAmNzB"}
1
+ {"version":3,"file":"hazo_get_auth.server.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/hazo_get_auth.server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK1C,OAAO,KAAK,EACV,cAAc,EAEd,eAAe,EAGhB,MAAM,cAAc,CAAC;AAuWtB;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,cAAc,CAAC,CAmNzB"}
@@ -108,7 +108,7 @@ async function fetch_user_data_from_db(user_id) {
108
108
  // v5.x: Use hazo_user_scopes instead of hazo_user_roles
109
109
  // Roles are now assigned per-scope via hazo_user_scopes.role_id
110
110
  const user_scopes_service = createCrudService(hazoConnect, "hazo_user_scopes", USER_SCOPES_CRUD_OPTIONS);
111
- const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions");
111
+ const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions", { primaryKeys: ["role_id", "permission_id"], autoId: false });
112
112
  const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
113
113
  // Fetch user
114
114
  const users = await users_service.findBy({ id: user_id });
@@ -1 +1 @@
1
- {"version":3,"file":"firm_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/firm_service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAIvD,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAIzE,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAWF;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAqEjE;AAoFD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,gBAAgB,CAAC,CA4E3B;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,kBAAkB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAmCjE"}
1
+ {"version":3,"file":"firm_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/firm_service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAIvD,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAIzE,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAWF;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAqEjE;AAqFD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,gBAAgB,CAAC,CA4E3B;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,kBAAkB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAmCjE"}
@@ -82,7 +82,7 @@ export async function ensure_owner_role(adapter) {
82
82
  async function assign_owner_permissions(adapter, role_id) {
83
83
  try {
84
84
  const permission_service = createCrudService(adapter, "hazo_permissions");
85
- const role_permission_service = createCrudService(adapter, "hazo_role_permissions");
85
+ const role_permission_service = createCrudService(adapter, "hazo_role_permissions", { primaryKeys: ["role_id", "permission_id"], autoId: false });
86
86
  // Default permissions for firm owner
87
87
  // firm_admin is a PERMISSION that grants administrative rights within a firm
88
88
  const permission_names = [
@@ -1 +1 @@
1
- {"version":3,"file":"user_management_permissions.d.ts","sourceRoot":"","sources":["../../../src/server/routes/user_management_permissions.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAQxD,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAGvC;;GAEG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;IAgE7C;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;;IAyJ9C;AAED;;GAEG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;IAkD7C;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,OAAO,EAAE,WAAW;;;;IAmEhD"}
1
+ {"version":3,"file":"user_management_permissions.d.ts","sourceRoot":"","sources":["../../../src/server/routes/user_management_permissions.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAQxD,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAGvC;;GAEG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;IAgE7C;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;;IAyJ9C;AAED;;GAEG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;IAkD7C;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,OAAO,EAAE,WAAW;;;;IAsEhD"}
@@ -226,7 +226,10 @@ export async function DELETE(request) {
226
226
  }
227
227
  const hazoConnect = get_hazo_connect_instance();
228
228
  const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
229
- const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions");
229
+ const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions", {
230
+ primaryKeys: ["role_id", "permission_id"],
231
+ autoId: false,
232
+ });
230
233
  // Check if permission is used in any role
231
234
  const role_permissions = await role_permissions_service.findBy({
232
235
  permission_id: permission_id_num,
@@ -1 +1 @@
1
- {"version":3,"file":"user_management_roles.d.ts","sourceRoot":"","sources":["../../../src/server/routes/user_management_roles.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AASxD,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAGvC;;GAEG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;;IAqF7C;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;;;IAgF9C;AAED;;GAEG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;IAgP7C"}
1
+ {"version":3,"file":"user_management_roles.d.ts","sourceRoot":"","sources":["../../../src/server/routes/user_management_roles.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AASxD,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAGvC;;GAEG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;;IAwF7C;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;;;IAgF9C;AAED;;GAEG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;IAmP7C"}
@@ -19,7 +19,10 @@ export async function GET(request) {
19
19
  const hazoConnect = get_hazo_connect_instance();
20
20
  const roles_service = createCrudService(hazoConnect, "hazo_roles");
21
21
  const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
22
- const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions");
22
+ const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions", {
23
+ primaryKeys: ["role_id", "permission_id"],
24
+ autoId: false,
25
+ });
23
26
  // Fetch all roles (empty object means no filter - get all records)
24
27
  const roles = await roles_service.findBy({});
25
28
  const permissions = await permissions_service.findBy({});
@@ -152,7 +155,10 @@ export async function PUT(request) {
152
155
  const hazoConnect = get_hazo_connect_instance();
153
156
  const roles_service = createCrudService(hazoConnect, "hazo_roles");
154
157
  const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
155
- const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions");
158
+ const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions", {
159
+ primaryKeys: ["role_id", "permission_id"],
160
+ autoId: false,
161
+ });
156
162
  // Get all permissions to build name-to-id map (empty object means no filter - get all records)
157
163
  const all_permissions = await permissions_service.findBy({});
158
164
  if (!Array.isArray(all_permissions)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hazo_auth",
3
- "version": "5.1.17",
3
+ "version": "5.1.19",
4
4
  "description": "Zero-config authentication UI components for Next.js with RBAC, OAuth, scope-based multi-tenancy, and invitations",
5
5
  "keywords": [
6
6
  "authentication",