hazo_auth 4.5.5 → 4.5.7
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 +282 -14
- package/SETUP_CHECKLIST.md +108 -21
- package/cli-src/lib/auth/auth_types.ts +2 -0
- package/cli-src/lib/auth/hazo_get_auth.server.ts +26 -0
- package/cli-src/lib/services/app_user_data_service.ts +294 -0
- package/cli-src/lib/services/index.ts +1 -0
- package/dist/app/api/hazo_auth/app_user_data/route.d.ts +64 -0
- package/dist/app/api/hazo_auth/app_user_data/route.d.ts.map +1 -0
- package/dist/app/api/hazo_auth/app_user_data/route.js +208 -0
- package/dist/app/api/hazo_auth/me/route.d.ts +2 -1
- package/dist/app/api/hazo_auth/me/route.d.ts.map +1 -1
- package/dist/app/api/hazo_auth/me/route.js +4 -1
- package/dist/components/layouts/app_user_data_test/index.d.ts +6 -0
- package/dist/components/layouts/app_user_data_test/index.d.ts.map +1 -0
- package/dist/components/layouts/app_user_data_test/index.js +145 -0
- package/dist/components/layouts/shared/components/password_field.js +1 -1
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +2 -2
- package/dist/components/layouts/shared/components/two_column_auth_layout.js +1 -1
- package/dist/lib/auth/auth_types.d.ts +1 -0
- 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 +24 -0
- package/dist/lib/services/app_user_data_service.d.ts +34 -0
- package/dist/lib/services/app_user_data_service.d.ts.map +1 -0
- package/dist/lib/services/app_user_data_service.js +228 -0
- package/dist/lib/services/index.d.ts +1 -0
- package/dist/lib/services/index.d.ts.map +1 -1
- package/dist/lib/services/index.js +1 -0
- package/dist/server/routes/app_user_data.d.ts +2 -0
- package/dist/server/routes/app_user_data.d.ts.map +1 -0
- package/dist/server/routes/app_user_data.js +2 -0
- package/dist/server/routes/index.d.ts +1 -0
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +2 -0
- package/dist/server_pages/forgot_password.d.ts.map +1 -1
- package/dist/server_pages/forgot_password.js +3 -2
- package/dist/server_pages/login.d.ts.map +1 -1
- package/dist/server_pages/login.js +8 -7
- package/dist/server_pages/my_settings.d.ts.map +1 -1
- package/dist/server_pages/my_settings.js +3 -2
- package/dist/server_pages/register.d.ts.map +1 -1
- package/dist/server_pages/register.js +3 -2
- package/dist/server_pages/reset_password.d.ts.map +1 -1
- package/dist/server_pages/reset_password.js +3 -2
- package/dist/server_pages/verify_email.d.ts.map +1 -1
- package/dist/server_pages/verify_email.js +3 -2
- package/package.json +26 -1
package/README.md
CHANGED
|
@@ -94,6 +94,7 @@ export default function Page() {
|
|
|
94
94
|
- ✅ Database connection initialized server-side via hazo_connect singleton
|
|
95
95
|
- ✅ Configuration loaded from hazo_auth_config.ini (or uses sensible defaults)
|
|
96
96
|
- ✅ All props automatically configured
|
|
97
|
+
- ✅ Navbar automatically rendered based on config (no manual wrapping needed)
|
|
97
98
|
- ✅ Page renders immediately - NO loading state!
|
|
98
99
|
|
|
99
100
|
**Available zero-config pages:**
|
|
@@ -398,16 +399,44 @@ Run the following SQL scripts in your PostgreSQL database:
|
|
|
398
399
|
```sql
|
|
399
400
|
-- Enum type for profile picture source
|
|
400
401
|
CREATE TYPE hazo_enum_profile_source_enum AS ENUM ('gravatar', 'custom', 'predefined');
|
|
402
|
+
|
|
403
|
+
-- Scope types enum (for HRBAC)
|
|
404
|
+
CREATE TYPE hazo_enum_scope_types AS ENUM (
|
|
405
|
+
'hazo_scopes_l1', 'hazo_scopes_l2', 'hazo_scopes_l3',
|
|
406
|
+
'hazo_scopes_l4', 'hazo_scopes_l5', 'hazo_scopes_l6', 'hazo_scopes_l7'
|
|
407
|
+
);
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
#### 2. Create the Organization Table (Multi-Tenancy)
|
|
411
|
+
|
|
412
|
+
```sql
|
|
413
|
+
-- Organization table for multi-tenancy (create before hazo_users)
|
|
414
|
+
CREATE TABLE hazo_org (
|
|
415
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
416
|
+
name TEXT NOT NULL,
|
|
417
|
+
parent_org_id UUID REFERENCES hazo_org(id) ON DELETE SET NULL,
|
|
418
|
+
root_org_id UUID REFERENCES hazo_org(id) ON DELETE SET NULL,
|
|
419
|
+
user_limit INTEGER NOT NULL DEFAULT 0,
|
|
420
|
+
active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
421
|
+
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
422
|
+
created_by UUID, -- Will reference hazo_users after it's created
|
|
423
|
+
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
424
|
+
changed_by UUID
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
CREATE INDEX idx_hazo_org_parent_org_id ON hazo_org(parent_org_id);
|
|
428
|
+
CREATE INDEX idx_hazo_org_root_org_id ON hazo_org(root_org_id);
|
|
429
|
+
CREATE INDEX idx_hazo_org_active ON hazo_org(active);
|
|
401
430
|
```
|
|
402
431
|
|
|
403
|
-
####
|
|
432
|
+
#### 3. Create the Users Table
|
|
404
433
|
|
|
405
434
|
```sql
|
|
406
435
|
-- Main users table
|
|
407
436
|
CREATE TABLE hazo_users (
|
|
408
437
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
409
438
|
email_address TEXT NOT NULL UNIQUE,
|
|
410
|
-
password_hash TEXT
|
|
439
|
+
password_hash TEXT, -- NULL for OAuth-only users
|
|
411
440
|
name TEXT,
|
|
412
441
|
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
|
413
442
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
@@ -417,17 +446,32 @@ CREATE TABLE hazo_users (
|
|
|
417
446
|
profile_source hazo_enum_profile_source_enum,
|
|
418
447
|
mfa_secret TEXT,
|
|
419
448
|
url_on_logon TEXT,
|
|
449
|
+
user_type TEXT, -- Optional user categorization
|
|
450
|
+
google_id TEXT UNIQUE, -- Google OAuth ID
|
|
451
|
+
auth_providers TEXT DEFAULT 'email', -- 'email', 'google', or 'email,google'
|
|
452
|
+
org_id UUID REFERENCES hazo_org(id) ON DELETE SET NULL,
|
|
453
|
+
root_org_id UUID REFERENCES hazo_org(id) ON DELETE SET NULL,
|
|
420
454
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
421
455
|
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
|
422
456
|
);
|
|
423
457
|
|
|
424
|
-
--
|
|
458
|
+
-- Indexes
|
|
425
459
|
CREATE INDEX idx_hazo_users_email ON hazo_users(email_address);
|
|
460
|
+
CREATE INDEX idx_hazo_users_user_type ON hazo_users(user_type);
|
|
461
|
+
CREATE UNIQUE INDEX idx_hazo_users_google_id ON hazo_users(google_id);
|
|
462
|
+
CREATE INDEX idx_hazo_users_org_id ON hazo_users(org_id);
|
|
463
|
+
CREATE INDEX idx_hazo_users_root_org_id ON hazo_users(root_org_id);
|
|
464
|
+
|
|
465
|
+
-- Add FK constraints to hazo_org after hazo_users exists
|
|
466
|
+
ALTER TABLE hazo_org ADD CONSTRAINT fk_hazo_org_created_by
|
|
467
|
+
FOREIGN KEY (created_by) REFERENCES hazo_users(id) ON DELETE SET NULL;
|
|
468
|
+
ALTER TABLE hazo_org ADD CONSTRAINT fk_hazo_org_changed_by
|
|
469
|
+
FOREIGN KEY (changed_by) REFERENCES hazo_users(id) ON DELETE SET NULL;
|
|
426
470
|
```
|
|
427
471
|
|
|
428
472
|
**Note:** The `url_on_logon` field is used to store a custom redirect URL for users after successful login. This allows per-user customization of post-login navigation.
|
|
429
473
|
|
|
430
|
-
####
|
|
474
|
+
#### 4. Create the Refresh Tokens Table
|
|
431
475
|
|
|
432
476
|
```sql
|
|
433
477
|
-- Refresh tokens table (used for password reset, email verification, etc.)
|
|
@@ -445,7 +489,7 @@ CREATE INDEX idx_hazo_refresh_tokens_user_id ON hazo_refresh_tokens(user_id);
|
|
|
445
489
|
CREATE INDEX idx_hazo_refresh_tokens_token_type ON hazo_refresh_tokens(token_type);
|
|
446
490
|
```
|
|
447
491
|
|
|
448
|
-
####
|
|
492
|
+
#### 5. Create the Permissions Table
|
|
449
493
|
|
|
450
494
|
```sql
|
|
451
495
|
-- Permissions table for RBAC
|
|
@@ -458,7 +502,7 @@ CREATE TABLE hazo_permissions (
|
|
|
458
502
|
);
|
|
459
503
|
```
|
|
460
504
|
|
|
461
|
-
####
|
|
505
|
+
#### 6. Create the Roles Table
|
|
462
506
|
|
|
463
507
|
```sql
|
|
464
508
|
-- Roles table for RBAC
|
|
@@ -470,7 +514,7 @@ CREATE TABLE hazo_roles (
|
|
|
470
514
|
);
|
|
471
515
|
```
|
|
472
516
|
|
|
473
|
-
####
|
|
517
|
+
#### 7. Create the Role-Permissions Junction Table
|
|
474
518
|
|
|
475
519
|
```sql
|
|
476
520
|
-- Junction table linking roles to permissions
|
|
@@ -487,7 +531,7 @@ CREATE INDEX idx_hazo_role_permissions_role_id ON hazo_role_permissions(role_id)
|
|
|
487
531
|
CREATE INDEX idx_hazo_role_permissions_permission_id ON hazo_role_permissions(permission_id);
|
|
488
532
|
```
|
|
489
533
|
|
|
490
|
-
####
|
|
534
|
+
#### 8. Create the User-Roles Junction Table
|
|
491
535
|
|
|
492
536
|
```sql
|
|
493
537
|
-- Junction table linking users to roles
|
|
@@ -513,14 +557,35 @@ For convenience, here's the complete SQL script to create all tables at once:
|
|
|
513
557
|
-- hazo_auth Database Setup Script (PostgreSQL)
|
|
514
558
|
-- ============================================
|
|
515
559
|
|
|
516
|
-
-- 1. Create enum
|
|
560
|
+
-- 1. Create enum types
|
|
517
561
|
CREATE TYPE hazo_enum_profile_source_enum AS ENUM ('gravatar', 'custom', 'predefined');
|
|
562
|
+
CREATE TYPE hazo_enum_scope_types AS ENUM (
|
|
563
|
+
'hazo_scopes_l1', 'hazo_scopes_l2', 'hazo_scopes_l3',
|
|
564
|
+
'hazo_scopes_l4', 'hazo_scopes_l5', 'hazo_scopes_l6', 'hazo_scopes_l7'
|
|
565
|
+
);
|
|
518
566
|
|
|
519
|
-
-- 2. Create
|
|
567
|
+
-- 2. Create organization table (multi-tenancy)
|
|
568
|
+
CREATE TABLE hazo_org (
|
|
569
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
570
|
+
name TEXT NOT NULL,
|
|
571
|
+
parent_org_id UUID REFERENCES hazo_org(id) ON DELETE SET NULL,
|
|
572
|
+
root_org_id UUID REFERENCES hazo_org(id) ON DELETE SET NULL,
|
|
573
|
+
user_limit INTEGER NOT NULL DEFAULT 0,
|
|
574
|
+
active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
575
|
+
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
576
|
+
created_by UUID,
|
|
577
|
+
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
578
|
+
changed_by UUID
|
|
579
|
+
);
|
|
580
|
+
CREATE INDEX idx_hazo_org_parent_org_id ON hazo_org(parent_org_id);
|
|
581
|
+
CREATE INDEX idx_hazo_org_root_org_id ON hazo_org(root_org_id);
|
|
582
|
+
CREATE INDEX idx_hazo_org_active ON hazo_org(active);
|
|
583
|
+
|
|
584
|
+
-- 3. Create users table
|
|
520
585
|
CREATE TABLE hazo_users (
|
|
521
586
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
522
587
|
email_address TEXT NOT NULL UNIQUE,
|
|
523
|
-
password_hash TEXT
|
|
588
|
+
password_hash TEXT,
|
|
524
589
|
name TEXT,
|
|
525
590
|
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
|
526
591
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
@@ -530,12 +595,27 @@ CREATE TABLE hazo_users (
|
|
|
530
595
|
profile_source hazo_enum_profile_source_enum,
|
|
531
596
|
mfa_secret TEXT,
|
|
532
597
|
url_on_logon TEXT,
|
|
598
|
+
user_type TEXT,
|
|
599
|
+
google_id TEXT UNIQUE,
|
|
600
|
+
auth_providers TEXT DEFAULT 'email',
|
|
601
|
+
org_id UUID REFERENCES hazo_org(id) ON DELETE SET NULL,
|
|
602
|
+
root_org_id UUID REFERENCES hazo_org(id) ON DELETE SET NULL,
|
|
533
603
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
534
604
|
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
|
535
605
|
);
|
|
536
606
|
CREATE INDEX idx_hazo_users_email ON hazo_users(email_address);
|
|
537
|
-
|
|
538
|
-
|
|
607
|
+
CREATE INDEX idx_hazo_users_user_type ON hazo_users(user_type);
|
|
608
|
+
CREATE UNIQUE INDEX idx_hazo_users_google_id ON hazo_users(google_id);
|
|
609
|
+
CREATE INDEX idx_hazo_users_org_id ON hazo_users(org_id);
|
|
610
|
+
CREATE INDEX idx_hazo_users_root_org_id ON hazo_users(root_org_id);
|
|
611
|
+
|
|
612
|
+
-- Add FK constraints to hazo_org after hazo_users exists
|
|
613
|
+
ALTER TABLE hazo_org ADD CONSTRAINT fk_hazo_org_created_by
|
|
614
|
+
FOREIGN KEY (created_by) REFERENCES hazo_users(id) ON DELETE SET NULL;
|
|
615
|
+
ALTER TABLE hazo_org ADD CONSTRAINT fk_hazo_org_changed_by
|
|
616
|
+
FOREIGN KEY (changed_by) REFERENCES hazo_users(id) ON DELETE SET NULL;
|
|
617
|
+
|
|
618
|
+
-- 4. Create refresh tokens table
|
|
539
619
|
CREATE TABLE hazo_refresh_tokens (
|
|
540
620
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
541
621
|
user_id UUID NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,
|
|
@@ -1101,6 +1181,8 @@ vertical_center = auto # 'auto' enables vertical centering when navbar is prese
|
|
|
1101
1181
|
|
|
1102
1182
|
### Authentication Page Navbar
|
|
1103
1183
|
|
|
1184
|
+
**The navbar now works automatically** - zero-config server page components include the navbar based on configuration without manual wrapping.
|
|
1185
|
+
|
|
1104
1186
|
When using `layout_mode = standalone`, you can enable a configurable navbar that appears on all auth pages:
|
|
1105
1187
|
|
|
1106
1188
|
```ini
|
|
@@ -1120,7 +1202,17 @@ height = 64 # Navbar height in pixels
|
|
|
1120
1202
|
|
|
1121
1203
|
The navbar provides consistent branding across authentication pages with your company logo, name, and optional home link. It automatically vertically centers auth content when enabled.
|
|
1122
1204
|
|
|
1123
|
-
**
|
|
1205
|
+
**Zero-config usage (recommended):**
|
|
1206
|
+
```typescript
|
|
1207
|
+
// app/hazo_auth/login/page.tsx
|
|
1208
|
+
import { LoginPage } from "hazo_auth/pages/login";
|
|
1209
|
+
|
|
1210
|
+
export default function Page() {
|
|
1211
|
+
return <LoginPage />; // Navbar appears automatically if enabled in config
|
|
1212
|
+
}
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
**Customize via props (advanced):**
|
|
1124
1216
|
```typescript
|
|
1125
1217
|
import { LoginLayout } from "hazo_auth/components/layouts/login";
|
|
1126
1218
|
|
|
@@ -1139,6 +1231,8 @@ export default function Page() {
|
|
|
1139
1231
|
|
|
1140
1232
|
**Disable for specific pages:**
|
|
1141
1233
|
```typescript
|
|
1234
|
+
<LoginPage disableNavbar={true} />
|
|
1235
|
+
// OR for layout components:
|
|
1142
1236
|
<LoginLayout navbar={{ enable_navbar: false }} />
|
|
1143
1237
|
```
|
|
1144
1238
|
|
|
@@ -1987,6 +2081,180 @@ For full documentation, see `CLAUDE.md` or `TECHDOC.md`.
|
|
|
1987
2081
|
|
|
1988
2082
|
---
|
|
1989
2083
|
|
|
2084
|
+
## App User Data (Custom User Metadata)
|
|
2085
|
+
|
|
2086
|
+
hazo_auth provides a flexible JSON field for storing custom user-specific data without modifying the `hazo_users` table schema. This allows consuming applications to store user preferences, settings, and app-specific state.
|
|
2087
|
+
|
|
2088
|
+
### Overview
|
|
2089
|
+
|
|
2090
|
+
- **JSON Storage**: Single TEXT column stores JSON objects (no schema restrictions)
|
|
2091
|
+
- **Deep Merge Support**: PATCH endpoint merges new data with existing data
|
|
2092
|
+
- **Full Replace**: PUT endpoint replaces entire JSON object
|
|
2093
|
+
- **Clear Data**: DELETE endpoint sets field to NULL
|
|
2094
|
+
- **Type-Safe**: TypeScript service layer with validation
|
|
2095
|
+
- **Included in Auth Response**: Available in `/api/hazo_auth/me` response
|
|
2096
|
+
|
|
2097
|
+
### Quick Start
|
|
2098
|
+
|
|
2099
|
+
1. **Run database migration**:
|
|
2100
|
+
```bash
|
|
2101
|
+
npm run migrate migrations/008_add_app_user_data_to_hazo_users.sql
|
|
2102
|
+
```
|
|
2103
|
+
|
|
2104
|
+
2. **Create API route** (`app/api/hazo_auth/app_user_data/route.ts`):
|
|
2105
|
+
```typescript
|
|
2106
|
+
export {
|
|
2107
|
+
appUserDataGET as GET,
|
|
2108
|
+
appUserDataPATCH as PATCH,
|
|
2109
|
+
appUserDataPUT as PUT,
|
|
2110
|
+
appUserDataDELETE as DELETE
|
|
2111
|
+
} from "hazo_auth/server/routes";
|
|
2112
|
+
```
|
|
2113
|
+
|
|
2114
|
+
Or use CLI: `npx hazo_auth generate-routes`
|
|
2115
|
+
|
|
2116
|
+
3. **Use in your app**:
|
|
2117
|
+
```typescript
|
|
2118
|
+
// Store user preferences (deep merge)
|
|
2119
|
+
PATCH /api/hazo_auth/app_user_data
|
|
2120
|
+
{
|
|
2121
|
+
data: {
|
|
2122
|
+
theme: "dark",
|
|
2123
|
+
language: "en-US",
|
|
2124
|
+
sidebar_collapsed: true
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
// Access in client components
|
|
2129
|
+
const { app_user_data } = use_hazo_auth();
|
|
2130
|
+
console.log(app_user_data?.theme); // "dark"
|
|
2131
|
+
```
|
|
2132
|
+
|
|
2133
|
+
### API Endpoints
|
|
2134
|
+
|
|
2135
|
+
**GET `/api/hazo_auth/app_user_data`** - Get current user's data
|
|
2136
|
+
```typescript
|
|
2137
|
+
Response: { data: { theme: "dark", sidebar_collapsed: true } | null }
|
|
2138
|
+
```
|
|
2139
|
+
|
|
2140
|
+
**PATCH `/api/hazo_auth/app_user_data`** - Merge with existing data (preserves other fields)
|
|
2141
|
+
```typescript
|
|
2142
|
+
Request: { data: { theme: "light" } }
|
|
2143
|
+
// If existing: { theme: "dark", sidebar_collapsed: true }
|
|
2144
|
+
// Result: { theme: "light", sidebar_collapsed: true }
|
|
2145
|
+
```
|
|
2146
|
+
|
|
2147
|
+
**PUT `/api/hazo_auth/app_user_data`** - Replace entire object
|
|
2148
|
+
```typescript
|
|
2149
|
+
Request: { data: { theme: "light" } }
|
|
2150
|
+
// Result: { theme: "light" } (sidebar_collapsed removed)
|
|
2151
|
+
```
|
|
2152
|
+
|
|
2153
|
+
**DELETE `/api/hazo_auth/app_user_data`** - Clear all data (sets to NULL)
|
|
2154
|
+
|
|
2155
|
+
### Service Functions
|
|
2156
|
+
|
|
2157
|
+
```typescript
|
|
2158
|
+
import {
|
|
2159
|
+
get_app_user_data,
|
|
2160
|
+
update_app_user_data,
|
|
2161
|
+
clear_app_user_data
|
|
2162
|
+
} from "hazo_auth";
|
|
2163
|
+
|
|
2164
|
+
// Get data
|
|
2165
|
+
const data = await get_app_user_data(adapter, user_id);
|
|
2166
|
+
|
|
2167
|
+
// Update with merge (default)
|
|
2168
|
+
await update_app_user_data(adapter, user_id, { theme: "light" }, true);
|
|
2169
|
+
|
|
2170
|
+
// Replace entirely
|
|
2171
|
+
await update_app_user_data(adapter, user_id, { theme: "light" }, false);
|
|
2172
|
+
|
|
2173
|
+
// Clear data
|
|
2174
|
+
await clear_app_user_data(adapter, user_id);
|
|
2175
|
+
```
|
|
2176
|
+
|
|
2177
|
+
### Access in `/api/hazo_auth/me`
|
|
2178
|
+
|
|
2179
|
+
The `app_user_data` field is included in the authentication response:
|
|
2180
|
+
|
|
2181
|
+
```typescript
|
|
2182
|
+
{
|
|
2183
|
+
authenticated: true,
|
|
2184
|
+
user: { ... },
|
|
2185
|
+
permissions: [...],
|
|
2186
|
+
app_user_data: { theme: "dark", sidebar_collapsed: true } | null
|
|
2187
|
+
}
|
|
2188
|
+
```
|
|
2189
|
+
|
|
2190
|
+
### Use Cases
|
|
2191
|
+
|
|
2192
|
+
**Store user preferences:**
|
|
2193
|
+
```typescript
|
|
2194
|
+
{
|
|
2195
|
+
theme: "dark",
|
|
2196
|
+
language: "en-US",
|
|
2197
|
+
timezone: "America/New_York"
|
|
2198
|
+
}
|
|
2199
|
+
```
|
|
2200
|
+
|
|
2201
|
+
**Store app-specific state:**
|
|
2202
|
+
```typescript
|
|
2203
|
+
{
|
|
2204
|
+
dashboard_layout: "grid",
|
|
2205
|
+
sidebar_collapsed: true,
|
|
2206
|
+
recent_searches: ["tax forms", "invoices"]
|
|
2207
|
+
}
|
|
2208
|
+
```
|
|
2209
|
+
|
|
2210
|
+
**Store nested configuration:**
|
|
2211
|
+
```typescript
|
|
2212
|
+
{
|
|
2213
|
+
notifications: {
|
|
2214
|
+
email: true,
|
|
2215
|
+
sms: false,
|
|
2216
|
+
push: true
|
|
2217
|
+
},
|
|
2218
|
+
privacy: {
|
|
2219
|
+
profile_public: false,
|
|
2220
|
+
show_email: false
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
```
|
|
2224
|
+
|
|
2225
|
+
### Deep Merge Behavior
|
|
2226
|
+
|
|
2227
|
+
```typescript
|
|
2228
|
+
// Existing data
|
|
2229
|
+
{ user: { name: "Alice", age: 30 }, theme: "dark" }
|
|
2230
|
+
|
|
2231
|
+
// PATCH with
|
|
2232
|
+
{ user: { age: 31 }, sidebar: true }
|
|
2233
|
+
|
|
2234
|
+
// Result (deep merge)
|
|
2235
|
+
{ user: { name: "Alice", age: 31 }, theme: "dark", sidebar: true }
|
|
2236
|
+
```
|
|
2237
|
+
|
|
2238
|
+
### Test Page
|
|
2239
|
+
|
|
2240
|
+
Visit `/hazo_auth/app_user_data_test` in your dev environment to test the API with an interactive UI:
|
|
2241
|
+
- View current data (live refresh)
|
|
2242
|
+
- Merge data (PATCH)
|
|
2243
|
+
- Replace data (PUT)
|
|
2244
|
+
- Clear data (DELETE)
|
|
2245
|
+
- JSON validation
|
|
2246
|
+
|
|
2247
|
+
### Performance & Limits
|
|
2248
|
+
|
|
2249
|
+
- **Recommended max size**: ~10KB per user (for preferences/settings)
|
|
2250
|
+
- **Storage**: JSON stored as TEXT (no compression)
|
|
2251
|
+
- **Caching**: Benefits from `hazo_get_auth()` LRU cache
|
|
2252
|
+
- **Large datasets**: Use separate tables for complex relational data
|
|
2253
|
+
|
|
2254
|
+
For full documentation, see `CHANGE_LOG.md` and `TECHDOC.md`.
|
|
2255
|
+
|
|
2256
|
+
---
|
|
2257
|
+
|
|
1990
2258
|
## User Profile Service
|
|
1991
2259
|
|
|
1992
2260
|
The `hazo_auth` package provides a batch user profile retrieval service for applications that need basic user information, such as chat applications or user lists.
|
package/SETUP_CHECKLIST.md
CHANGED
|
@@ -312,15 +312,54 @@ Run this SQL script in your PostgreSQL database:
|
|
|
312
312
|
-- Ensure we're in the public schema (or your target schema)
|
|
313
313
|
SET search_path TO public;
|
|
314
314
|
|
|
315
|
-
-- Create enum
|
|
315
|
+
-- Create enum types (drop first if they exist to avoid conflicts)
|
|
316
316
|
DROP TYPE IF EXISTS hazo_enum_profile_source_enum CASCADE;
|
|
317
317
|
CREATE TYPE hazo_enum_profile_source_enum AS ENUM ('gravatar', 'custom', 'predefined');
|
|
318
318
|
|
|
319
|
+
DROP TYPE IF EXISTS hazo_enum_scope_types CASCADE;
|
|
320
|
+
CREATE TYPE hazo_enum_scope_types AS ENUM (
|
|
321
|
+
'hazo_scopes_l1', 'hazo_scopes_l2', 'hazo_scopes_l3',
|
|
322
|
+
'hazo_scopes_l4', 'hazo_scopes_l5', 'hazo_scopes_l6', 'hazo_scopes_l7'
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
DROP TYPE IF EXISTS hazo_enum_notify_chain_status CASCADE;
|
|
326
|
+
CREATE TYPE hazo_enum_notify_chain_status AS ENUM ('draft', 'published', 'inactive');
|
|
327
|
+
|
|
328
|
+
DROP TYPE IF EXISTS hazo_enum_notify_email_type CASCADE;
|
|
329
|
+
CREATE TYPE hazo_enum_notify_email_type AS ENUM ('system', 'user');
|
|
330
|
+
|
|
331
|
+
DROP TYPE IF EXISTS hazo_enum_group_type CASCADE;
|
|
332
|
+
CREATE TYPE hazo_enum_group_type AS ENUM ('support', 'peer', 'group');
|
|
333
|
+
|
|
334
|
+
DROP TYPE IF EXISTS hazo_enum_group_role CASCADE;
|
|
335
|
+
CREATE TYPE hazo_enum_group_role AS ENUM ('client', 'staff', 'owner', 'admin', 'member');
|
|
336
|
+
|
|
337
|
+
DROP TYPE IF EXISTS hazo_enum_chat_type CASCADE;
|
|
338
|
+
CREATE TYPE hazo_enum_chat_type AS ENUM ('chat', 'field', 'project', 'support', 'general');
|
|
339
|
+
|
|
340
|
+
-- Create organization table (multi-tenancy) - MUST be created before hazo_users
|
|
341
|
+
CREATE TABLE hazo_org (
|
|
342
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
343
|
+
name TEXT NOT NULL,
|
|
344
|
+
parent_org_id UUID REFERENCES hazo_org(id) ON DELETE SET NULL,
|
|
345
|
+
root_org_id UUID REFERENCES hazo_org(id) ON DELETE SET NULL,
|
|
346
|
+
user_limit INTEGER NOT NULL DEFAULT 0, -- 0 = unlimited
|
|
347
|
+
active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
348
|
+
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
349
|
+
created_by UUID, -- FK added after hazo_users exists
|
|
350
|
+
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
351
|
+
changed_by UUID -- FK added after hazo_users exists
|
|
352
|
+
);
|
|
353
|
+
CREATE INDEX idx_hazo_org_parent_org_id ON hazo_org(parent_org_id);
|
|
354
|
+
CREATE INDEX idx_hazo_org_root_org_id ON hazo_org(root_org_id);
|
|
355
|
+
CREATE INDEX idx_hazo_org_active ON hazo_org(active);
|
|
356
|
+
CREATE INDEX idx_hazo_org_name ON hazo_org(name);
|
|
357
|
+
|
|
319
358
|
-- Create users table
|
|
320
359
|
CREATE TABLE hazo_users (
|
|
321
360
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
322
361
|
email_address TEXT NOT NULL UNIQUE,
|
|
323
|
-
password_hash TEXT
|
|
362
|
+
password_hash TEXT, -- NULL for OAuth-only users
|
|
324
363
|
name TEXT,
|
|
325
364
|
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
|
326
365
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
@@ -330,10 +369,25 @@ CREATE TABLE hazo_users (
|
|
|
330
369
|
profile_source hazo_enum_profile_source_enum,
|
|
331
370
|
mfa_secret TEXT,
|
|
332
371
|
url_on_logon TEXT,
|
|
372
|
+
user_type TEXT, -- Optional user categorization
|
|
373
|
+
google_id TEXT UNIQUE, -- Google OAuth ID
|
|
374
|
+
auth_providers TEXT DEFAULT 'email', -- 'email', 'google', or 'email,google'
|
|
375
|
+
org_id UUID REFERENCES hazo_org(id) ON DELETE SET NULL,
|
|
376
|
+
root_org_id UUID REFERENCES hazo_org(id) ON DELETE SET NULL,
|
|
333
377
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
334
378
|
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
|
335
379
|
);
|
|
336
380
|
CREATE INDEX idx_hazo_users_email ON hazo_users(email_address);
|
|
381
|
+
CREATE INDEX idx_hazo_users_user_type ON hazo_users(user_type);
|
|
382
|
+
CREATE UNIQUE INDEX idx_hazo_users_google_id ON hazo_users(google_id);
|
|
383
|
+
CREATE INDEX idx_hazo_users_org_id ON hazo_users(org_id);
|
|
384
|
+
CREATE INDEX idx_hazo_users_root_org_id ON hazo_users(root_org_id);
|
|
385
|
+
|
|
386
|
+
-- Add FK constraints to hazo_org now that hazo_users exists
|
|
387
|
+
ALTER TABLE hazo_org ADD CONSTRAINT fk_hazo_org_created_by
|
|
388
|
+
FOREIGN KEY (created_by) REFERENCES hazo_users(id) ON DELETE SET NULL;
|
|
389
|
+
ALTER TABLE hazo_org ADD CONSTRAINT fk_hazo_org_changed_by
|
|
390
|
+
FOREIGN KEY (changed_by) REFERENCES hazo_users(id) ON DELETE SET NULL;
|
|
337
391
|
|
|
338
392
|
-- Create refresh tokens table
|
|
339
393
|
CREATE TABLE hazo_refresh_tokens (
|
|
@@ -453,7 +507,22 @@ GRANT USAGE ON TYPE hazo_enum_profile_source_enum TO anon, authenticated;
|
|
|
453
507
|
|
|
454
508
|
**Checklist:**
|
|
455
509
|
- [ ] Database created (SQLite file or PostgreSQL)
|
|
456
|
-
- [ ] All
|
|
510
|
+
- [ ] All enum types created (PostgreSQL only):
|
|
511
|
+
- [ ] `hazo_enum_profile_source_enum`
|
|
512
|
+
- [ ] `hazo_enum_scope_types`
|
|
513
|
+
- [ ] `hazo_enum_notify_chain_status`
|
|
514
|
+
- [ ] `hazo_enum_notify_email_type`
|
|
515
|
+
- [ ] `hazo_enum_group_type`
|
|
516
|
+
- [ ] `hazo_enum_group_role`
|
|
517
|
+
- [ ] `hazo_enum_chat_type`
|
|
518
|
+
- [ ] All core tables exist:
|
|
519
|
+
- [ ] `hazo_org` (multi-tenancy - must be created before hazo_users)
|
|
520
|
+
- [ ] `hazo_users` (with google_id, auth_providers, org_id, root_org_id, user_type fields)
|
|
521
|
+
- [ ] `hazo_refresh_tokens`
|
|
522
|
+
- [ ] `hazo_permissions`
|
|
523
|
+
- [ ] `hazo_roles`
|
|
524
|
+
- [ ] `hazo_role_permissions`
|
|
525
|
+
- [ ] `hazo_user_roles`
|
|
457
526
|
|
|
458
527
|
---
|
|
459
528
|
|
|
@@ -1244,25 +1313,29 @@ $$ LANGUAGE plpgsql;
|
|
|
1244
1313
|
CREATE TABLE hazo_scopes_l1 (
|
|
1245
1314
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1246
1315
|
seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l1'),
|
|
1247
|
-
|
|
1316
|
+
org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
|
|
1317
|
+
root_org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
|
|
1248
1318
|
name TEXT NOT NULL,
|
|
1249
1319
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
1250
1320
|
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
|
1251
1321
|
);
|
|
1252
|
-
CREATE INDEX
|
|
1322
|
+
CREATE INDEX idx_hazo_scopes_l1_org_id ON hazo_scopes_l1(org_id);
|
|
1323
|
+
CREATE INDEX idx_hazo_scopes_l1_root_org_id ON hazo_scopes_l1(root_org_id);
|
|
1253
1324
|
CREATE INDEX idx_hazo_scopes_l1_seq ON hazo_scopes_l1(seq);
|
|
1254
1325
|
|
|
1255
1326
|
-- Level 2 (parent: L1)
|
|
1256
1327
|
CREATE TABLE hazo_scopes_l2 (
|
|
1257
1328
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1258
1329
|
seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l2'),
|
|
1259
|
-
|
|
1330
|
+
org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
|
|
1331
|
+
root_org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
|
|
1260
1332
|
name TEXT NOT NULL,
|
|
1261
1333
|
parent_scope_id UUID REFERENCES hazo_scopes_l1(id) ON DELETE CASCADE,
|
|
1262
1334
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
1263
1335
|
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
|
1264
1336
|
);
|
|
1265
|
-
CREATE INDEX
|
|
1337
|
+
CREATE INDEX idx_hazo_scopes_l2_org_id ON hazo_scopes_l2(org_id);
|
|
1338
|
+
CREATE INDEX idx_hazo_scopes_l2_root_org_id ON hazo_scopes_l2(root_org_id);
|
|
1266
1339
|
CREATE INDEX idx_hazo_scopes_l2_seq ON hazo_scopes_l2(seq);
|
|
1267
1340
|
CREATE INDEX idx_hazo_scopes_l2_parent ON hazo_scopes_l2(parent_scope_id);
|
|
1268
1341
|
|
|
@@ -1270,13 +1343,15 @@ CREATE INDEX idx_hazo_scopes_l2_parent ON hazo_scopes_l2(parent_scope_id);
|
|
|
1270
1343
|
CREATE TABLE hazo_scopes_l3 (
|
|
1271
1344
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1272
1345
|
seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l3'),
|
|
1273
|
-
|
|
1346
|
+
org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
|
|
1347
|
+
root_org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
|
|
1274
1348
|
name TEXT NOT NULL,
|
|
1275
1349
|
parent_scope_id UUID REFERENCES hazo_scopes_l2(id) ON DELETE CASCADE,
|
|
1276
1350
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
1277
1351
|
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
|
1278
1352
|
);
|
|
1279
|
-
CREATE INDEX
|
|
1353
|
+
CREATE INDEX idx_hazo_scopes_l3_org_id ON hazo_scopes_l3(org_id);
|
|
1354
|
+
CREATE INDEX idx_hazo_scopes_l3_root_org_id ON hazo_scopes_l3(root_org_id);
|
|
1280
1355
|
CREATE INDEX idx_hazo_scopes_l3_seq ON hazo_scopes_l3(seq);
|
|
1281
1356
|
CREATE INDEX idx_hazo_scopes_l3_parent ON hazo_scopes_l3(parent_scope_id);
|
|
1282
1357
|
|
|
@@ -1284,13 +1359,15 @@ CREATE INDEX idx_hazo_scopes_l3_parent ON hazo_scopes_l3(parent_scope_id);
|
|
|
1284
1359
|
CREATE TABLE hazo_scopes_l4 (
|
|
1285
1360
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1286
1361
|
seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l4'),
|
|
1287
|
-
|
|
1362
|
+
org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
|
|
1363
|
+
root_org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
|
|
1288
1364
|
name TEXT NOT NULL,
|
|
1289
1365
|
parent_scope_id UUID REFERENCES hazo_scopes_l3(id) ON DELETE CASCADE,
|
|
1290
1366
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
1291
1367
|
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
|
1292
1368
|
);
|
|
1293
|
-
CREATE INDEX
|
|
1369
|
+
CREATE INDEX idx_hazo_scopes_l4_org_id ON hazo_scopes_l4(org_id);
|
|
1370
|
+
CREATE INDEX idx_hazo_scopes_l4_root_org_id ON hazo_scopes_l4(root_org_id);
|
|
1294
1371
|
CREATE INDEX idx_hazo_scopes_l4_seq ON hazo_scopes_l4(seq);
|
|
1295
1372
|
CREATE INDEX idx_hazo_scopes_l4_parent ON hazo_scopes_l4(parent_scope_id);
|
|
1296
1373
|
|
|
@@ -1298,13 +1375,15 @@ CREATE INDEX idx_hazo_scopes_l4_parent ON hazo_scopes_l4(parent_scope_id);
|
|
|
1298
1375
|
CREATE TABLE hazo_scopes_l5 (
|
|
1299
1376
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1300
1377
|
seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l5'),
|
|
1301
|
-
|
|
1378
|
+
org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
|
|
1379
|
+
root_org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
|
|
1302
1380
|
name TEXT NOT NULL,
|
|
1303
1381
|
parent_scope_id UUID REFERENCES hazo_scopes_l4(id) ON DELETE CASCADE,
|
|
1304
1382
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
1305
1383
|
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
|
1306
1384
|
);
|
|
1307
|
-
CREATE INDEX
|
|
1385
|
+
CREATE INDEX idx_hazo_scopes_l5_org_id ON hazo_scopes_l5(org_id);
|
|
1386
|
+
CREATE INDEX idx_hazo_scopes_l5_root_org_id ON hazo_scopes_l5(root_org_id);
|
|
1308
1387
|
CREATE INDEX idx_hazo_scopes_l5_seq ON hazo_scopes_l5(seq);
|
|
1309
1388
|
CREATE INDEX idx_hazo_scopes_l5_parent ON hazo_scopes_l5(parent_scope_id);
|
|
1310
1389
|
|
|
@@ -1312,13 +1391,15 @@ CREATE INDEX idx_hazo_scopes_l5_parent ON hazo_scopes_l5(parent_scope_id);
|
|
|
1312
1391
|
CREATE TABLE hazo_scopes_l6 (
|
|
1313
1392
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1314
1393
|
seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l6'),
|
|
1315
|
-
|
|
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,
|
|
1316
1396
|
name TEXT NOT NULL,
|
|
1317
1397
|
parent_scope_id UUID REFERENCES hazo_scopes_l5(id) ON DELETE CASCADE,
|
|
1318
1398
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
1319
1399
|
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
|
1320
1400
|
);
|
|
1321
|
-
CREATE INDEX
|
|
1401
|
+
CREATE INDEX idx_hazo_scopes_l6_org_id ON hazo_scopes_l6(org_id);
|
|
1402
|
+
CREATE INDEX idx_hazo_scopes_l6_root_org_id ON hazo_scopes_l6(root_org_id);
|
|
1322
1403
|
CREATE INDEX idx_hazo_scopes_l6_seq ON hazo_scopes_l6(seq);
|
|
1323
1404
|
CREATE INDEX idx_hazo_scopes_l6_parent ON hazo_scopes_l6(parent_scope_id);
|
|
1324
1405
|
|
|
@@ -1326,13 +1407,15 @@ CREATE INDEX idx_hazo_scopes_l6_parent ON hazo_scopes_l6(parent_scope_id);
|
|
|
1326
1407
|
CREATE TABLE hazo_scopes_l7 (
|
|
1327
1408
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1328
1409
|
seq TEXT NOT NULL DEFAULT hazo_scope_id_generator('hazo_scopes_l7'),
|
|
1329
|
-
|
|
1410
|
+
org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
|
|
1411
|
+
root_org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
|
|
1330
1412
|
name TEXT NOT NULL,
|
|
1331
1413
|
parent_scope_id UUID REFERENCES hazo_scopes_l6(id) ON DELETE CASCADE,
|
|
1332
1414
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
1333
1415
|
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
|
1334
1416
|
);
|
|
1335
|
-
CREATE INDEX
|
|
1417
|
+
CREATE INDEX idx_hazo_scopes_l7_org_id ON hazo_scopes_l7(org_id);
|
|
1418
|
+
CREATE INDEX idx_hazo_scopes_l7_root_org_id ON hazo_scopes_l7(root_org_id);
|
|
1336
1419
|
CREATE INDEX idx_hazo_scopes_l7_seq ON hazo_scopes_l7(seq);
|
|
1337
1420
|
CREATE INDEX idx_hazo_scopes_l7_parent ON hazo_scopes_l7(parent_scope_id);
|
|
1338
1421
|
|
|
@@ -1353,14 +1436,14 @@ CREATE INDEX idx_hazo_user_scopes_scope_type ON hazo_user_scopes(scope_type);
|
|
|
1353
1436
|
-- 5. Create scope labels table
|
|
1354
1437
|
CREATE TABLE hazo_scope_labels (
|
|
1355
1438
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1356
|
-
|
|
1439
|
+
org_id UUID NOT NULL REFERENCES hazo_org(id) ON DELETE CASCADE,
|
|
1357
1440
|
scope_type hazo_enum_scope_types NOT NULL,
|
|
1358
1441
|
label TEXT NOT NULL,
|
|
1359
1442
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
1360
1443
|
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
1361
|
-
UNIQUE(
|
|
1444
|
+
UNIQUE(org_id, scope_type)
|
|
1362
1445
|
);
|
|
1363
|
-
CREATE INDEX
|
|
1446
|
+
CREATE INDEX idx_hazo_scope_labels_org_id ON hazo_scope_labels(org_id);
|
|
1364
1447
|
```
|
|
1365
1448
|
|
|
1366
1449
|
#### PostgreSQL Grant Scripts
|
|
@@ -1522,7 +1605,11 @@ sqlite3 data/hazo_auth.sqlite ".tables" | grep -E "hazo_scopes|hazo_user_scopes|
|
|
|
1522
1605
|
**HRBAC Checklist:**
|
|
1523
1606
|
- [ ] `enable_hrbac = true` in config
|
|
1524
1607
|
- [ ] HRBAC permissions added to defaults
|
|
1525
|
-
- [ ] All 9 HRBAC tables created
|
|
1608
|
+
- [ ] All 9 HRBAC tables created:
|
|
1609
|
+
- [ ] `hazo_scopes_l1` through `hazo_scopes_l7` (with org_id, root_org_id FKs)
|
|
1610
|
+
- [ ] `hazo_user_scopes` (junction table)
|
|
1611
|
+
- [ ] `hazo_scope_labels` (custom labels per org)
|
|
1612
|
+
- [ ] Scope ID generator function created (PostgreSQL)
|
|
1526
1613
|
- [ ] Grants applied (PostgreSQL)
|
|
1527
1614
|
- [ ] HRBAC tabs visible in User Management
|
|
1528
1615
|
- [ ] Scope test page works
|
|
@@ -10,6 +10,8 @@ export type HazoAuthUser = {
|
|
|
10
10
|
email_address: string;
|
|
11
11
|
is_active: boolean;
|
|
12
12
|
profile_picture_url: string | null;
|
|
13
|
+
// App-specific user data (JSON object stored as TEXT in database)
|
|
14
|
+
app_user_data: Record<string, unknown> | null;
|
|
13
15
|
// Multi-tenancy fields (only populated when multi-tenancy is enabled)
|
|
14
16
|
org_id?: string | null;
|
|
15
17
|
org_name?: string | null;
|