hazo_auth 3.0.4 → 4.1.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.
Files changed (82) hide show
  1. package/README.md +228 -8
  2. package/SETUP_CHECKLIST.md +370 -0
  3. package/dist/app/api/hazo_auth/me/route.d.ts +3 -0
  4. package/dist/app/api/hazo_auth/me/route.d.ts.map +1 -1
  5. package/dist/app/api/hazo_auth/me/route.js +9 -1
  6. package/dist/components/layouts/my_settings/components/profile_picture_library_tab.d.ts.map +1 -1
  7. package/dist/components/layouts/my_settings/components/profile_picture_library_tab.js +2 -2
  8. package/dist/components/layouts/profile_stamp_test/index.d.ts +10 -0
  9. package/dist/components/layouts/profile_stamp_test/index.d.ts.map +1 -0
  10. package/dist/components/layouts/profile_stamp_test/index.js +51 -0
  11. package/dist/components/layouts/rbac_test/index.d.ts +15 -0
  12. package/dist/components/layouts/rbac_test/index.d.ts.map +1 -0
  13. package/dist/components/layouts/rbac_test/index.js +378 -0
  14. package/dist/components/layouts/shared/components/password_field.js +1 -1
  15. package/dist/components/layouts/shared/components/profile_stamp.d.ts +58 -0
  16. package/dist/components/layouts/shared/components/profile_stamp.d.ts.map +1 -0
  17. package/dist/components/layouts/shared/components/profile_stamp.js +72 -0
  18. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
  19. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +2 -2
  20. package/dist/components/layouts/shared/components/two_column_auth_layout.js +1 -1
  21. package/dist/components/layouts/shared/hooks/use_auth_status.d.ts +3 -0
  22. package/dist/components/layouts/shared/hooks/use_auth_status.d.ts.map +1 -1
  23. package/dist/components/layouts/shared/hooks/use_auth_status.js +4 -0
  24. package/dist/components/layouts/shared/index.d.ts +2 -0
  25. package/dist/components/layouts/shared/index.d.ts.map +1 -1
  26. package/dist/components/layouts/shared/index.js +1 -0
  27. package/dist/components/layouts/user_management/components/roles_matrix.d.ts +2 -3
  28. package/dist/components/layouts/user_management/components/roles_matrix.d.ts.map +1 -1
  29. package/dist/components/layouts/user_management/components/roles_matrix.js +133 -8
  30. package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts +12 -0
  31. package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts.map +1 -0
  32. package/dist/components/layouts/user_management/components/scope_hierarchy_tab.js +291 -0
  33. package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts +13 -0
  34. package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts.map +1 -0
  35. package/dist/components/layouts/user_management/components/scope_labels_tab.js +158 -0
  36. package/dist/components/layouts/user_management/components/user_scopes_tab.d.ts +11 -0
  37. package/dist/components/layouts/user_management/components/user_scopes_tab.d.ts.map +1 -0
  38. package/dist/components/layouts/user_management/components/user_scopes_tab.js +267 -0
  39. package/dist/components/layouts/user_management/index.d.ts +9 -2
  40. package/dist/components/layouts/user_management/index.d.ts.map +1 -1
  41. package/dist/components/layouts/user_management/index.js +22 -6
  42. package/dist/components/ui/hover-card.d.ts +7 -0
  43. package/dist/components/ui/hover-card.d.ts.map +1 -0
  44. package/dist/components/ui/hover-card.js +29 -0
  45. package/dist/components/ui/index.d.ts +1 -0
  46. package/dist/components/ui/index.d.ts.map +1 -1
  47. package/dist/components/ui/index.js +1 -0
  48. package/dist/components/ui/select.d.ts +14 -0
  49. package/dist/components/ui/select.d.ts.map +1 -0
  50. package/dist/components/ui/select.js +59 -0
  51. package/dist/components/ui/tree-view.d.ts +108 -0
  52. package/dist/components/ui/tree-view.d.ts.map +1 -0
  53. package/dist/components/ui/tree-view.js +194 -0
  54. package/dist/lib/auth/auth_types.d.ts +45 -0
  55. package/dist/lib/auth/auth_types.d.ts.map +1 -1
  56. package/dist/lib/auth/auth_types.js +13 -0
  57. package/dist/lib/auth/hazo_get_auth.server.d.ts +4 -2
  58. package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
  59. package/dist/lib/auth/hazo_get_auth.server.js +107 -3
  60. package/dist/lib/auth/scope_cache.d.ts +92 -0
  61. package/dist/lib/auth/scope_cache.d.ts.map +1 -0
  62. package/dist/lib/auth/scope_cache.js +171 -0
  63. package/dist/lib/scope_hierarchy_config.server.d.ts +39 -0
  64. package/dist/lib/scope_hierarchy_config.server.d.ts.map +1 -0
  65. package/dist/lib/scope_hierarchy_config.server.js +96 -0
  66. package/dist/lib/services/email_service.d.ts.map +1 -1
  67. package/dist/lib/services/email_service.js +7 -2
  68. package/dist/lib/services/profile_picture_service.d.ts +1 -7
  69. package/dist/lib/services/profile_picture_service.d.ts.map +1 -1
  70. package/dist/lib/services/profile_picture_service.js +77 -32
  71. package/dist/lib/services/registration_service.js +1 -1
  72. package/dist/lib/services/scope_labels_service.d.ts +48 -0
  73. package/dist/lib/services/scope_labels_service.d.ts.map +1 -0
  74. package/dist/lib/services/scope_labels_service.js +277 -0
  75. package/dist/lib/services/scope_service.d.ts +114 -0
  76. package/dist/lib/services/scope_service.d.ts.map +1 -0
  77. package/dist/lib/services/scope_service.js +582 -0
  78. package/dist/lib/services/user_scope_service.d.ts +74 -0
  79. package/dist/lib/services/user_scope_service.d.ts.map +1 -0
  80. package/dist/lib/services/user_scope_service.js +415 -0
  81. package/hazo_auth_config.example.ini +1 -1
  82. package/package.json +4 -1
package/README.md CHANGED
@@ -198,11 +198,12 @@ import { MySettingsLayout } from "hazo_auth/components/layouts/my_settings";
198
198
  import { UserManagementLayout } from "hazo_auth/components/layouts/user_management";
199
199
 
200
200
  // Import shared components and hooks from barrel export
201
- import {
202
- ProfilePicMenu,
201
+ import {
202
+ ProfilePicMenu,
203
203
  ProfilePicMenuWrapper,
204
- use_hazo_auth,
205
- use_auth_status
204
+ ProfileStamp,
205
+ use_hazo_auth,
206
+ use_auth_status
206
207
  } from "hazo_auth/components/layouts/shared";
207
208
 
208
209
  // Import server-side utilities
@@ -258,11 +259,12 @@ For client components (browser-safe, no Node.js dependencies):
258
259
 
259
260
  ```typescript
260
261
  // Use hazo_auth/client for client components
261
- import {
262
- ProfilePicMenu,
263
- use_auth_status,
262
+ import {
263
+ ProfilePicMenu,
264
+ ProfileStamp,
265
+ use_auth_status,
264
266
  use_hazo_auth,
265
- cn
267
+ cn
266
268
  } from "hazo_auth/client";
267
269
  ```
268
270
 
@@ -706,6 +708,7 @@ import { ResetPasswordLayout } from "hazo_auth/components/layouts/reset_password
706
708
  import { EmailVerificationLayout } from "hazo_auth/components/layouts/email_verification";
707
709
  import { MySettingsLayout } from "hazo_auth/components/layouts/my_settings";
708
710
  import { UserManagementLayout } from "hazo_auth/components/layouts/user_management";
711
+ import { RbacTestLayout } from "hazo_auth/components/layouts/rbac_test";
709
712
 
710
713
  // Shared layout components and hooks (barrel import - recommended)
711
714
  import {
@@ -762,6 +765,7 @@ export default async function LoginPage() {
762
765
  - `EmailVerificationLayout` - Verify email address
763
766
  - `MySettingsLayout` - User profile and settings
764
767
  - `UserManagementLayout` - Admin user/role management (requires user_management API routes)
768
+ - `RbacTestLayout` - RBAC/HRBAC permission and scope testing tool (requires admin_test_access permission)
765
769
 
766
770
  ### User Management Component
767
771
 
@@ -793,6 +797,7 @@ export { GET, POST, PUT } from "hazo_auth/server/routes";
793
797
  - **Users:** List users, deactivate users, send password reset emails
794
798
  - **Permissions:** List permissions (from DB and config), migrate config permissions to DB, create/update/delete permissions
795
799
  - **Roles:** List roles with permissions, create roles, update role-permission assignments
800
+ - **UI Enhancement**: The Roles tab uses a tag-based UI for better readability. Each role displays permissions as inline tags/chips (showing up to 4, with "+N more" to expand). Edit permissions via an interactive dialog with Select All/Unselect All buttons.
796
801
  - **User Roles:** Get user roles, assign roles to users, bulk update user role assignments
797
802
 
798
803
  **Example Usage:**
@@ -860,6 +865,10 @@ This is the **standardized endpoint** that ensures consistent response format ac
860
865
  last_logon: string | undefined,
861
866
  profile_picture_url: string | null,
862
867
  profile_source: "upload" | "library" | "gravatar" | "custom" | undefined,
868
+ // Profile picture aliases (for consuming app compatibility)
869
+ profile_image?: string, // Alias for profile_picture_url
870
+ avatar_url?: string, // Alias for profile_picture_url
871
+ image?: string, // Alias for profile_picture_url
863
872
  // Permissions (always included)
864
873
  user: {
865
874
  id: string,
@@ -1229,6 +1238,217 @@ enable_friendly_error_messages = true
1229
1238
 
1230
1239
  ---
1231
1240
 
1241
+ ## Hierarchical Role-Based Access Control (HRBAC)
1242
+
1243
+ hazo_auth supports optional Hierarchical Role-Based Access Control (HRBAC) with 7 scope levels (L1-L7). HRBAC extends standard RBAC by allowing users to be assigned to scopes in a hierarchy, with automatic inheritance of access to child scopes.
1244
+
1245
+ ### Enabling HRBAC
1246
+
1247
+ Add the following to your `hazo_auth_config.ini`:
1248
+
1249
+ ```ini
1250
+ [hazo_auth__scope_hierarchy]
1251
+ enable_hrbac = true
1252
+ default_org = my_organization # Optional: default organization for single-tenant apps
1253
+ scope_cache_ttl_minutes = 15
1254
+ scope_cache_max_entries = 5000
1255
+
1256
+ # Optional: customize default labels for each scope level
1257
+ default_label_l1 = Company
1258
+ default_label_l2 = Division
1259
+ default_label_l3 = Department
1260
+ default_label_l4 = Team
1261
+ default_label_l5 = Project
1262
+ default_label_l6 = Sub-project
1263
+ default_label_l7 = Task
1264
+ ```
1265
+
1266
+ ### Database Setup
1267
+
1268
+ HRBAC requires additional database tables. See `SETUP_CHECKLIST.md` for full PostgreSQL and SQLite scripts, including:
1269
+ - `hazo_scopes_l1` through `hazo_scopes_l7` - Scope tables with parent_scope_id references
1270
+ - `hazo_user_scopes` - User-scope assignments
1271
+ - `hazo_scope_labels` - Custom labels per organization
1272
+ - `hazo_enum_scope_types` - Enum type for scope validation
1273
+
1274
+ ### Using hazo_get_auth with Scope Options
1275
+
1276
+ When HRBAC is enabled, you can check scope access alongside permissions:
1277
+
1278
+ ```typescript
1279
+ import { hazo_get_auth } from "hazo_auth/lib/auth/hazo_get_auth.server";
1280
+ import { ScopeAccessError } from "hazo_auth/lib/auth/auth_types";
1281
+
1282
+ export async function GET(request: NextRequest) {
1283
+ try {
1284
+ const authResult = await hazo_get_auth(request, {
1285
+ required_permissions: ["view_reports"],
1286
+ scope_type: "hazo_scopes_l3", // Check access to Level 3 scope
1287
+ scope_seq: "L3_001", // Scope identifier (or use scope_id for UUID)
1288
+ strict: true, // Throws ScopeAccessError if denied
1289
+ });
1290
+
1291
+ if (!authResult.authenticated) {
1292
+ return NextResponse.json({ error: "Authentication required" }, { status: 401 });
1293
+ }
1294
+
1295
+ // Both permission_ok and scope_ok must be true for full access
1296
+ if (authResult.scope_ok) {
1297
+ // Access granted - scope_access_via shows how access was granted
1298
+ console.log("Access via:", authResult.scope_access_via);
1299
+ }
1300
+
1301
+ return NextResponse.json({ message: "Access granted" });
1302
+ } catch (error) {
1303
+ if (error instanceof ScopeAccessError) {
1304
+ return NextResponse.json(
1305
+ { error: "Scope access denied", scope: error.scope_identifier },
1306
+ { status: 403 }
1307
+ );
1308
+ }
1309
+ throw error;
1310
+ }
1311
+ }
1312
+ ```
1313
+
1314
+ ### Scope Access Inheritance
1315
+
1316
+ Users assigned to a higher-level scope automatically have access to all descendant scopes:
1317
+ - User with L2 scope access can access all L3, L4, L5, L6, L7 scopes under that L2 scope
1318
+ - Direct assignments take precedence over inherited access
1319
+ - The `scope_access_via` field in the result shows which scope granted access
1320
+
1321
+ ### Required Permissions for Management
1322
+
1323
+ - `admin_scope_hierarchy_management` - Manage scopes and scope labels
1324
+ - `admin_user_scope_assignment` - Assign scopes to users
1325
+ - `admin_test_access` - Access the RBAC/HRBAC test tool
1326
+
1327
+ Add these to your `application_permission_list_defaults` in `hazo_auth_config.ini`:
1328
+
1329
+ ```ini
1330
+ [hazo_auth__user_management]
1331
+ application_permission_list_defaults = admin_user_management,admin_role_management,admin_permission_management,admin_scope_hierarchy_management,admin_user_scope_assignment,admin_test_access
1332
+ ```
1333
+
1334
+ ### User Management UI
1335
+
1336
+ When HRBAC is enabled and the user has appropriate permissions, three new tabs appear in the User Management layout:
1337
+ - **Scope Hierarchy** - Create, edit, and delete scopes at each level
1338
+ - **Scope Labels** - Customize labels for scope levels per organization
1339
+ - **User Scopes** - Assign and remove scope assignments for users
1340
+
1341
+ ### RBAC/HRBAC Test Tool
1342
+
1343
+ The `RbacTestLayout` component provides a comprehensive testing interface for administrators to test RBAC permissions and HRBAC scope access for any user in the system.
1344
+
1345
+ **Features:**
1346
+ - **User Selection**: Dropdown to select any user in the system
1347
+ - **User Info Display**: Shows selected user's current permissions and assigned scopes
1348
+ - **RBAC Test Tab**: Select permissions to test if the user has them
1349
+ - **HRBAC Test Tab**: Select a scope from a tree view and test if the user has access
1350
+ - **Results Display**: Clear pass/fail indicators with missing permissions and scope access details
1351
+
1352
+ **Required Permission:** `admin_test_access`
1353
+
1354
+ **Usage in Your App:**
1355
+
1356
+ ```typescript
1357
+ // app/admin/rbac-test/page.tsx
1358
+ import { RbacTestLayout } from "hazo_auth/components/layouts/rbac_test";
1359
+ import { is_hrbac_enabled, get_default_org } from "hazo_auth/lib/scope_hierarchy_config.server";
1360
+
1361
+ export default function RbacTestPage() {
1362
+ const hrbacEnabled = is_hrbac_enabled();
1363
+ const defaultOrg = get_default_org();
1364
+
1365
+ return (
1366
+ <RbacTestLayout
1367
+ hrbacEnabled={hrbacEnabled}
1368
+ defaultOrg={defaultOrg}
1369
+ />
1370
+ );
1371
+ }
1372
+ ```
1373
+
1374
+ **API Route Required:**
1375
+ The test tool uses the `/api/hazo_auth/rbac_test` endpoint which is included in the package. This route:
1376
+ - Accepts `test_user_id` parameter to test any user
1377
+ - Checks permissions and scope access for the specified user
1378
+ - Requires `admin_test_access` permission to call
1379
+
1380
+ **Demo Page:** A test page is available at `/hazo_auth/rbac_test` in the demo app.
1381
+
1382
+ ---
1383
+
1384
+ ## ProfileStamp Component
1385
+
1386
+ The `ProfileStamp` component is a drop-in widget that displays a circular profile picture with a hover card showing user details. Perfect for adding profile attribution to notes, comments, or any user-generated content.
1387
+
1388
+ ### Features
1389
+
1390
+ - Displays user's profile picture or initials
1391
+ - Hover card with user name, email, and custom fields
1392
+ - Three sizes: sm (24px), default (32px), lg (40px)
1393
+ - Automatic loading state and unauthenticated fallback
1394
+ - Fully accessible with keyboard navigation
1395
+
1396
+ ### Usage
1397
+
1398
+ ```typescript
1399
+ import { ProfileStamp } from "hazo_auth/client";
1400
+
1401
+ // Basic usage
1402
+ <ProfileStamp />
1403
+
1404
+ // With custom size and fields
1405
+ <ProfileStamp
1406
+ size="lg"
1407
+ custom_fields={[
1408
+ { label: "Role", value: "Admin" },
1409
+ { label: "Department", value: "Engineering" }
1410
+ ]}
1411
+ />
1412
+
1413
+ // Hide default fields, only show custom fields
1414
+ <ProfileStamp
1415
+ show_name={false}
1416
+ show_email={false}
1417
+ custom_fields={[
1418
+ { label: "Posted", value: "2 hours ago" }
1419
+ ]}
1420
+ />
1421
+ ```
1422
+
1423
+ ### Props
1424
+
1425
+ | Prop | Type | Default | Description |
1426
+ |------|------|---------|-------------|
1427
+ | `size` | `"sm" \| "default" \| "lg"` | `"default"` | Avatar size (sm: 24px, default: 32px, lg: 40px) |
1428
+ | `custom_fields` | `ProfileStampCustomField[]` | `[]` | Custom fields to display in hover card |
1429
+ | `className` | `string` | `undefined` | Additional CSS classes |
1430
+ | `show_name` | `boolean` | `true` | Show user name in hover card |
1431
+ | `show_email` | `boolean` | `true` | Show email in hover card |
1432
+
1433
+ ### ProfileStampCustomField Type
1434
+
1435
+ ```typescript
1436
+ type ProfileStampCustomField = {
1437
+ label: string; // Field label (e.g., "Role", "Department")
1438
+ value: string; // Field value (e.g., "Admin", "Engineering")
1439
+ };
1440
+ ```
1441
+
1442
+ ### Test Page
1443
+
1444
+ Visit `/hazo_auth/profile_stamp_test` in your dev environment to see examples of ProfileStamp with various configurations:
1445
+ - Size variants
1446
+ - Custom fields
1447
+ - Display options (showing/hiding name and email)
1448
+ - Usage scenarios (notes, comments, activity feeds)
1449
+
1450
+ ---
1451
+
1232
1452
  ## Profile Picture Menu Widget
1233
1453
 
1234
1454
  The Profile Picture Menu is a versatile component for navbar or sidebar that automatically displays:
@@ -954,6 +954,376 @@ Visit each page and verify it loads:
954
954
  - [ ] `http://localhost:3000/hazo_auth/register` - Registration form displays
955
955
  - [ ] `http://localhost:3000/hazo_auth/forgot_password` - Forgot password form displays
956
956
  - [ ] `http://localhost:3000/hazo_auth/my_settings` - Settings page displays (after login)
957
+ - [ ] `http://localhost:3000/hazo_auth/profile_stamp_test` - ProfileStamp component examples display
958
+
959
+ ---
960
+
961
+ ## Phase 7: HRBAC Setup (Optional)
962
+
963
+ Hierarchical Role-Based Access Control (HRBAC) extends the standard RBAC with 7 hierarchical scope levels. This phase is optional - only complete it if you need scope-based access control.
964
+
965
+ ### Step 7.1: Enable HRBAC in Configuration
966
+
967
+ Add to your `hazo_auth_config.ini`:
968
+
969
+ ```ini
970
+ [hazo_auth__scope_hierarchy]
971
+ enable_hrbac = true
972
+ default_org = my_organization
973
+ scope_cache_ttl_minutes = 15
974
+ scope_cache_max_entries = 5000
975
+
976
+ # Optional: customize default labels for each scope level
977
+ default_label_l1 = Company
978
+ default_label_l2 = Division
979
+ default_label_l3 = Department
980
+ default_label_l4 = Team
981
+ default_label_l5 = Project
982
+ default_label_l6 = Sub-project
983
+ default_label_l7 = Task
984
+ ```
985
+
986
+ ### Step 7.2: Add HRBAC Permissions
987
+
988
+ Add the HRBAC management permissions to your `application_permission_list_defaults`:
989
+
990
+ ```ini
991
+ [hazo_auth__user_management]
992
+ application_permission_list_defaults = admin_user_management,admin_role_management,admin_permission_management,admin_scope_hierarchy_management,admin_user_scope_assignment
993
+ ```
994
+
995
+ ### Step 7.3: Create HRBAC Database Tables
996
+
997
+ #### PostgreSQL
998
+
999
+ ```sql
1000
+ -- =============================================
1001
+ -- HRBAC Database Setup Script (PostgreSQL)
1002
+ -- =============================================
1003
+
1004
+ -- 1. Create scope types enum
1005
+ CREATE TYPE hazo_enum_scope_types AS ENUM (
1006
+ 'hazo_scopes_l1',
1007
+ 'hazo_scopes_l2',
1008
+ 'hazo_scopes_l3',
1009
+ 'hazo_scopes_l4',
1010
+ 'hazo_scopes_l5',
1011
+ 'hazo_scopes_l6',
1012
+ 'hazo_scopes_l7'
1013
+ );
1014
+
1015
+ -- 2. Create sequence generator function for scope IDs
1016
+ CREATE OR REPLACE FUNCTION hazo_scope_id_generator(table_name TEXT)
1017
+ RETURNS TEXT AS $$
1018
+ DECLARE
1019
+ prefix TEXT;
1020
+ next_num INTEGER;
1021
+ result TEXT;
1022
+ BEGIN
1023
+ -- Extract level number from table name (e.g., 'hazo_scopes_l3' -> '3')
1024
+ prefix := 'L' || SUBSTRING(table_name FROM 'hazo_scopes_l([0-9]+)');
1025
+
1026
+ -- Get the next sequence number
1027
+ EXECUTE format(
1028
+ 'SELECT COALESCE(MAX(CAST(SUBSTRING(seq FROM ''L[0-9]+_([0-9]+)'') AS INTEGER)), 0) + 1 FROM %I',
1029
+ table_name
1030
+ ) INTO next_num;
1031
+
1032
+ -- Format as L{level}_{padded_number}
1033
+ result := prefix || '_' || LPAD(next_num::TEXT, 3, '0');
1034
+
1035
+ RETURN result;
1036
+ END;
1037
+ $$ LANGUAGE plpgsql;
1038
+
1039
+ -- 3. Create scope tables (L1 through L7)
1040
+
1041
+ -- Level 1 (top level - no parent)
1042
+ CREATE TABLE hazo_scopes_l1 (
1043
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1044
+ seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l1'),
1045
+ org TEXT NOT NULL,
1046
+ name TEXT NOT NULL,
1047
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1048
+ changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
1049
+ );
1050
+ CREATE INDEX idx_hazo_scopes_l1_org ON hazo_scopes_l1(org);
1051
+ CREATE INDEX idx_hazo_scopes_l1_seq ON hazo_scopes_l1(seq);
1052
+
1053
+ -- Level 2 (parent: L1)
1054
+ CREATE TABLE hazo_scopes_l2 (
1055
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1056
+ seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l2'),
1057
+ org TEXT NOT NULL,
1058
+ name TEXT NOT NULL,
1059
+ parent_scope_id UUID REFERENCES hazo_scopes_l1(id) ON DELETE CASCADE,
1060
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1061
+ changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
1062
+ );
1063
+ CREATE INDEX idx_hazo_scopes_l2_org ON hazo_scopes_l2(org);
1064
+ CREATE INDEX idx_hazo_scopes_l2_seq ON hazo_scopes_l2(seq);
1065
+ CREATE INDEX idx_hazo_scopes_l2_parent ON hazo_scopes_l2(parent_scope_id);
1066
+
1067
+ -- Level 3 (parent: L2)
1068
+ CREATE TABLE hazo_scopes_l3 (
1069
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1070
+ seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l3'),
1071
+ org TEXT NOT NULL,
1072
+ name TEXT NOT NULL,
1073
+ parent_scope_id UUID REFERENCES hazo_scopes_l2(id) ON DELETE CASCADE,
1074
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1075
+ changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
1076
+ );
1077
+ CREATE INDEX idx_hazo_scopes_l3_org ON hazo_scopes_l3(org);
1078
+ CREATE INDEX idx_hazo_scopes_l3_seq ON hazo_scopes_l3(seq);
1079
+ CREATE INDEX idx_hazo_scopes_l3_parent ON hazo_scopes_l3(parent_scope_id);
1080
+
1081
+ -- Level 4 (parent: L3)
1082
+ CREATE TABLE hazo_scopes_l4 (
1083
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1084
+ seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l4'),
1085
+ org TEXT NOT NULL,
1086
+ name TEXT NOT NULL,
1087
+ parent_scope_id UUID REFERENCES hazo_scopes_l3(id) ON DELETE CASCADE,
1088
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1089
+ changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
1090
+ );
1091
+ CREATE INDEX idx_hazo_scopes_l4_org ON hazo_scopes_l4(org);
1092
+ CREATE INDEX idx_hazo_scopes_l4_seq ON hazo_scopes_l4(seq);
1093
+ CREATE INDEX idx_hazo_scopes_l4_parent ON hazo_scopes_l4(parent_scope_id);
1094
+
1095
+ -- Level 5 (parent: L4)
1096
+ CREATE TABLE hazo_scopes_l5 (
1097
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1098
+ seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l5'),
1099
+ org TEXT NOT NULL,
1100
+ name TEXT NOT NULL,
1101
+ parent_scope_id UUID REFERENCES hazo_scopes_l4(id) ON DELETE CASCADE,
1102
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1103
+ changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
1104
+ );
1105
+ CREATE INDEX idx_hazo_scopes_l5_org ON hazo_scopes_l5(org);
1106
+ CREATE INDEX idx_hazo_scopes_l5_seq ON hazo_scopes_l5(seq);
1107
+ CREATE INDEX idx_hazo_scopes_l5_parent ON hazo_scopes_l5(parent_scope_id);
1108
+
1109
+ -- Level 6 (parent: L5)
1110
+ CREATE TABLE hazo_scopes_l6 (
1111
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1112
+ seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l6'),
1113
+ org TEXT NOT NULL,
1114
+ name TEXT NOT NULL,
1115
+ parent_scope_id UUID REFERENCES hazo_scopes_l5(id) ON DELETE CASCADE,
1116
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1117
+ changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
1118
+ );
1119
+ CREATE INDEX idx_hazo_scopes_l6_org ON hazo_scopes_l6(org);
1120
+ CREATE INDEX idx_hazo_scopes_l6_seq ON hazo_scopes_l6(seq);
1121
+ CREATE INDEX idx_hazo_scopes_l6_parent ON hazo_scopes_l6(parent_scope_id);
1122
+
1123
+ -- Level 7 (parent: L6)
1124
+ CREATE TABLE hazo_scopes_l7 (
1125
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1126
+ seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l7'),
1127
+ org TEXT NOT NULL,
1128
+ name TEXT NOT NULL,
1129
+ parent_scope_id UUID REFERENCES hazo_scopes_l6(id) ON DELETE CASCADE,
1130
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1131
+ changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
1132
+ );
1133
+ CREATE INDEX idx_hazo_scopes_l7_org ON hazo_scopes_l7(org);
1134
+ CREATE INDEX idx_hazo_scopes_l7_seq ON hazo_scopes_l7(seq);
1135
+ CREATE INDEX idx_hazo_scopes_l7_parent ON hazo_scopes_l7(parent_scope_id);
1136
+
1137
+ -- 4. Create user scopes junction table
1138
+ CREATE TABLE hazo_user_scopes (
1139
+ user_id UUID NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,
1140
+ scope_id UUID NOT NULL,
1141
+ scope_seq TEXT NOT NULL,
1142
+ scope_type hazo_enum_scope_types NOT NULL,
1143
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1144
+ changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1145
+ PRIMARY KEY (user_id, scope_type, scope_id)
1146
+ );
1147
+ CREATE INDEX idx_hazo_user_scopes_user_id ON hazo_user_scopes(user_id);
1148
+ CREATE INDEX idx_hazo_user_scopes_scope_id ON hazo_user_scopes(scope_id);
1149
+ CREATE INDEX idx_hazo_user_scopes_scope_type ON hazo_user_scopes(scope_type);
1150
+
1151
+ -- 5. Create scope labels table
1152
+ CREATE TABLE hazo_scope_labels (
1153
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1154
+ org TEXT NOT NULL,
1155
+ scope_type hazo_enum_scope_types NOT NULL,
1156
+ label TEXT NOT NULL,
1157
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1158
+ changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
1159
+ UNIQUE(org, scope_type)
1160
+ );
1161
+ CREATE INDEX idx_hazo_scope_labels_org ON hazo_scope_labels(org);
1162
+ ```
1163
+
1164
+ #### PostgreSQL Grant Scripts
1165
+
1166
+ After creating the tables, grant appropriate permissions:
1167
+
1168
+ ```sql
1169
+ -- Grant to your admin user (replace 'your_admin_user' with actual username)
1170
+ GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l1 TO your_admin_user;
1171
+ GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l2 TO your_admin_user;
1172
+ GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l3 TO your_admin_user;
1173
+ GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l4 TO your_admin_user;
1174
+ GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l5 TO your_admin_user;
1175
+ GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l6 TO your_admin_user;
1176
+ GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l7 TO your_admin_user;
1177
+ GRANT ALL PRIVILEGES ON TABLE hazo_user_scopes TO your_admin_user;
1178
+ GRANT ALL PRIVILEGES ON TABLE hazo_scope_labels TO your_admin_user;
1179
+ GRANT USAGE ON TYPE hazo_enum_scope_types TO your_admin_user;
1180
+ GRANT EXECUTE ON FUNCTION hazo_scope_id_generator(TEXT) TO your_admin_user;
1181
+
1182
+ -- For PostgREST authenticated role
1183
+ GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l1 TO authenticated;
1184
+ GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l2 TO authenticated;
1185
+ GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l3 TO authenticated;
1186
+ GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l4 TO authenticated;
1187
+ GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l5 TO authenticated;
1188
+ GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l6 TO authenticated;
1189
+ GRANT ALL PRIVILEGES ON TABLE hazo_scopes_l7 TO authenticated;
1190
+ GRANT ALL PRIVILEGES ON TABLE hazo_user_scopes TO authenticated;
1191
+ GRANT ALL PRIVILEGES ON TABLE hazo_scope_labels TO authenticated;
1192
+ GRANT USAGE ON TYPE hazo_enum_scope_types TO authenticated;
1193
+ GRANT EXECUTE ON FUNCTION hazo_scope_id_generator(TEXT) TO authenticated;
1194
+ ```
1195
+
1196
+ #### SQLite
1197
+
1198
+ ```sql
1199
+ -- =============================================
1200
+ -- HRBAC Database Setup Script (SQLite)
1201
+ -- =============================================
1202
+
1203
+ -- Scope tables (L1 through L7)
1204
+
1205
+ CREATE TABLE IF NOT EXISTS hazo_scopes_l1 (
1206
+ id TEXT PRIMARY KEY,
1207
+ seq TEXT NOT NULL,
1208
+ org TEXT NOT NULL,
1209
+ name TEXT NOT NULL,
1210
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1211
+ changed_at TEXT NOT NULL DEFAULT (datetime('now'))
1212
+ );
1213
+
1214
+ CREATE TABLE IF NOT EXISTS hazo_scopes_l2 (
1215
+ id TEXT PRIMARY KEY,
1216
+ seq TEXT NOT NULL,
1217
+ org TEXT NOT NULL,
1218
+ name TEXT NOT NULL,
1219
+ parent_scope_id TEXT REFERENCES hazo_scopes_l1(id) ON DELETE CASCADE,
1220
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1221
+ changed_at TEXT NOT NULL DEFAULT (datetime('now'))
1222
+ );
1223
+
1224
+ CREATE TABLE IF NOT EXISTS hazo_scopes_l3 (
1225
+ id TEXT PRIMARY KEY,
1226
+ seq TEXT NOT NULL,
1227
+ org TEXT NOT NULL,
1228
+ name TEXT NOT NULL,
1229
+ parent_scope_id TEXT REFERENCES hazo_scopes_l2(id) ON DELETE CASCADE,
1230
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1231
+ changed_at TEXT NOT NULL DEFAULT (datetime('now'))
1232
+ );
1233
+
1234
+ CREATE TABLE IF NOT EXISTS hazo_scopes_l4 (
1235
+ id TEXT PRIMARY KEY,
1236
+ seq TEXT NOT NULL,
1237
+ org TEXT NOT NULL,
1238
+ name TEXT NOT NULL,
1239
+ parent_scope_id TEXT REFERENCES hazo_scopes_l3(id) ON DELETE CASCADE,
1240
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1241
+ changed_at TEXT NOT NULL DEFAULT (datetime('now'))
1242
+ );
1243
+
1244
+ CREATE TABLE IF NOT EXISTS hazo_scopes_l5 (
1245
+ id TEXT PRIMARY KEY,
1246
+ seq TEXT NOT NULL,
1247
+ org TEXT NOT NULL,
1248
+ name TEXT NOT NULL,
1249
+ parent_scope_id TEXT REFERENCES hazo_scopes_l4(id) ON DELETE CASCADE,
1250
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1251
+ changed_at TEXT NOT NULL DEFAULT (datetime('now'))
1252
+ );
1253
+
1254
+ CREATE TABLE IF NOT EXISTS hazo_scopes_l6 (
1255
+ id TEXT PRIMARY KEY,
1256
+ seq TEXT NOT NULL,
1257
+ org TEXT NOT NULL,
1258
+ name TEXT NOT NULL,
1259
+ parent_scope_id TEXT REFERENCES hazo_scopes_l5(id) ON DELETE CASCADE,
1260
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1261
+ changed_at TEXT NOT NULL DEFAULT (datetime('now'))
1262
+ );
1263
+
1264
+ CREATE TABLE IF NOT EXISTS hazo_scopes_l7 (
1265
+ id TEXT PRIMARY KEY,
1266
+ seq TEXT NOT NULL,
1267
+ org TEXT NOT NULL,
1268
+ name TEXT NOT NULL,
1269
+ parent_scope_id TEXT REFERENCES hazo_scopes_l6(id) ON DELETE CASCADE,
1270
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1271
+ changed_at TEXT NOT NULL DEFAULT (datetime('now'))
1272
+ );
1273
+
1274
+ -- User scopes junction table
1275
+ CREATE TABLE IF NOT EXISTS hazo_user_scopes (
1276
+ user_id TEXT NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,
1277
+ scope_id TEXT NOT NULL,
1278
+ scope_seq TEXT NOT NULL,
1279
+ 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')),
1280
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1281
+ changed_at TEXT NOT NULL DEFAULT (datetime('now')),
1282
+ PRIMARY KEY (user_id, scope_type, scope_id)
1283
+ );
1284
+
1285
+ -- Scope labels table
1286
+ CREATE TABLE IF NOT EXISTS hazo_scope_labels (
1287
+ id TEXT PRIMARY KEY,
1288
+ org TEXT NOT NULL,
1289
+ 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')),
1290
+ label TEXT NOT NULL,
1291
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1292
+ changed_at TEXT NOT NULL DEFAULT (datetime('now')),
1293
+ UNIQUE(org, scope_type)
1294
+ );
1295
+ ```
1296
+
1297
+ ### Step 7.4: Verify HRBAC Tables
1298
+
1299
+ #### PostgreSQL
1300
+ ```sql
1301
+ SELECT table_name FROM information_schema.tables
1302
+ WHERE table_name LIKE 'hazo_scopes_%'
1303
+ OR table_name IN ('hazo_user_scopes', 'hazo_scope_labels');
1304
+ -- Expected: 9 tables (7 scope tables + user_scopes + scope_labels)
1305
+ ```
1306
+
1307
+ #### SQLite
1308
+ ```bash
1309
+ sqlite3 data/hazo_auth.sqlite ".tables" | grep -E "hazo_scopes|hazo_user_scopes|hazo_scope_labels"
1310
+ ```
1311
+
1312
+ ### Step 7.5: Test HRBAC
1313
+
1314
+ 1. Start your dev server: `npm run dev`
1315
+ 2. Log in with a user that has `admin_scope_hierarchy_management` permission
1316
+ 3. Visit `/hazo_auth/user_management`
1317
+ 4. Verify the "Scope Hierarchy", "Scope Labels", and "User Scopes" tabs appear
1318
+ 5. Visit `/hazo_auth/scope_test` to test scope access checking
1319
+
1320
+ **HRBAC Checklist:**
1321
+ - [ ] `enable_hrbac = true` in config
1322
+ - [ ] HRBAC permissions added to defaults
1323
+ - [ ] All 9 HRBAC tables created
1324
+ - [ ] Grants applied (PostgreSQL)
1325
+ - [ ] HRBAC tabs visible in User Management
1326
+ - [ ] Scope test page works
957
1327
 
958
1328
  ---
959
1329
 
@@ -14,6 +14,9 @@ import { NextRequest, NextResponse } from "next/server";
14
14
  * email_verified: boolean,
15
15
  * last_logon: string | undefined,
16
16
  * profile_picture_url: string | null,
17
+ * profile_image: string | null, // alias for profile_picture_url
18
+ * avatar_url: string | null, // alias for profile_picture_url
19
+ * image: string | null, // alias for profile_picture_url
17
20
  * profile_source: "upload" | "library" | "gravatar" | "custom" | undefined,
18
21
  * user: { id, email_address, name, is_active, profile_picture_url },
19
22
  * permissions: string[],
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../src/app/api/hazo_auth/me/route.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AASxD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;IA0E7C"}
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../src/app/api/hazo_auth/me/route.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AASxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;IA+E7C"}