hazo_auth 1.0.4 → 1.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.
- package/README.md +72 -1
- package/hazo_auth_config.example.ini +41 -0
- package/instrumentation.ts +2 -2
- package/package.json +4 -3
- package/scripts/init_users.ts +378 -0
- package/src/app/api/hazo_auth/auth/upload_profile_picture/route.ts +8 -8
- package/src/app/api/hazo_auth/change_password/route.ts +7 -7
- package/src/app/api/hazo_auth/forgot_password/route.ts +4 -4
- package/src/app/api/hazo_auth/get_auth/route.ts +4 -4
- package/src/app/api/hazo_auth/invalidate_cache/route.ts +5 -5
- package/src/app/api/hazo_auth/library_photos/route.ts +3 -3
- package/src/app/api/hazo_auth/login/route.ts +31 -5
- package/src/app/api/hazo_auth/logout/route.ts +4 -4
- package/src/app/api/hazo_auth/me/route.ts +1 -1
- package/src/app/api/hazo_auth/profile_picture/[filename]/route.ts +1 -1
- package/src/app/api/hazo_auth/register/route.ts +17 -14
- package/src/app/api/hazo_auth/remove_profile_picture/route.ts +5 -5
- package/src/app/api/hazo_auth/resend_verification/route.ts +4 -4
- package/src/app/api/hazo_auth/reset_password/route.ts +5 -5
- package/src/app/api/hazo_auth/update_user/route.ts +5 -5
- package/src/app/api/hazo_auth/upload_profile_picture/route.ts +8 -8
- package/src/app/api/hazo_auth/user_management/permissions/route.ts +4 -4
- package/src/app/api/hazo_auth/user_management/roles/route.ts +5 -5
- package/src/app/api/hazo_auth/user_management/users/roles/route.ts +5 -5
- package/src/app/api/hazo_auth/user_management/users/route.ts +6 -6
- package/src/app/api/hazo_auth/validate_reset_token/route.ts +4 -4
- package/src/app/api/hazo_auth/verify_email/route.ts +4 -4
- package/src/app/api/migrations/apply/route.ts +3 -3
- package/src/app/hazo_auth/forgot_password/forgot_password_page_client.tsx +4 -4
- package/src/app/hazo_auth/forgot_password/page.tsx +4 -4
- package/src/app/hazo_auth/login/login_page_client.tsx +20 -5
- package/src/app/hazo_auth/login/page.tsx +17 -5
- package/src/app/hazo_auth/my_settings/my_settings_page_client.tsx +3 -3
- package/src/app/hazo_auth/my_settings/page.tsx +4 -4
- package/src/app/hazo_auth/register/page.tsx +15 -5
- package/src/app/hazo_auth/register/register_page_client.tsx +13 -4
- package/src/app/hazo_auth/reset_password/page.tsx +4 -4
- package/src/app/hazo_auth/reset_password/reset_password_page_client.tsx +4 -4
- package/src/app/hazo_auth/user_management/page.tsx +3 -3
- package/src/app/hazo_auth/user_management/user_management_page_client.tsx +1 -1
- package/src/app/hazo_auth/verify_email/page.tsx +4 -4
- package/src/app/hazo_auth/verify_email/verify_email_page_client.tsx +4 -4
- package/src/app/hazo_connect/api/sqlite/data/route.ts +1 -1
- package/src/app/hazo_connect/api/sqlite/schema/route.ts +1 -1
- package/src/app/hazo_connect/api/sqlite/tables/route.ts +1 -1
- package/src/app/layout.tsx +1 -1
- package/src/app/page.tsx +1 -1
- package/src/components/layouts/email_verification/config/email_verification_field_config.ts +2 -2
- package/src/components/layouts/email_verification/hooks/use_email_verification.ts +3 -3
- package/src/components/layouts/email_verification/index.tsx +11 -11
- package/src/components/layouts/forgot_password/config/forgot_password_field_config.ts +2 -2
- package/src/components/layouts/forgot_password/hooks/use_forgot_password_form.ts +3 -3
- package/src/components/layouts/forgot_password/index.tsx +10 -10
- package/src/components/layouts/login/config/login_field_config.ts +2 -2
- package/src/components/layouts/login/hooks/use_login_form.ts +18 -13
- package/src/components/layouts/login/index.tsx +39 -11
- package/src/components/layouts/my_settings/components/editable_field.tsx +3 -3
- package/src/components/layouts/my_settings/components/password_change_dialog.tsx +5 -5
- package/src/components/layouts/my_settings/components/profile_picture_dialog.tsx +4 -4
- package/src/components/layouts/my_settings/components/profile_picture_display.tsx +2 -2
- package/src/components/layouts/my_settings/components/profile_picture_gravatar_tab.tsx +3 -3
- package/src/components/layouts/my_settings/components/profile_picture_library_tab.tsx +5 -5
- package/src/components/layouts/my_settings/components/profile_picture_upload_tab.tsx +4 -4
- package/src/components/layouts/my_settings/config/my_settings_field_config.ts +2 -2
- package/src/components/layouts/my_settings/hooks/use_my_settings.ts +2 -2
- package/src/components/layouts/my_settings/index.tsx +5 -5
- package/src/components/layouts/register/config/register_field_config.ts +2 -2
- package/src/components/layouts/register/hooks/use_register_form.ts +8 -5
- package/src/components/layouts/register/index.tsx +29 -11
- package/src/components/layouts/reset_password/config/reset_password_field_config.ts +2 -2
- package/src/components/layouts/reset_password/hooks/use_reset_password_form.ts +4 -4
- package/src/components/layouts/reset_password/index.tsx +10 -10
- package/src/components/layouts/shared/components/auth_page_shell.tsx +36 -0
- package/src/components/layouts/shared/components/standalone_layout_wrapper.tsx +53 -0
- package/src/components/layouts/user_management/components/roles_matrix.tsx +7 -7
- package/src/components/layouts/user_management/index.tsx +10 -10
- package/src/components/ui/alert-dialog.tsx +2 -2
- package/src/components/ui/avatar.tsx +1 -1
- package/src/components/ui/button.tsx +1 -1
- package/src/components/ui/checkbox.tsx +1 -1
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/dropdown-menu.tsx +1 -1
- package/src/components/ui/hazo_ui_tooltip.tsx +1 -1
- package/src/components/ui/input.tsx +1 -1
- package/src/components/ui/label.tsx +1 -1
- package/src/components/ui/separator.tsx +1 -1
- package/src/components/ui/sheet.tsx +1 -1
- package/src/components/ui/sidebar.tsx +8 -8
- package/src/components/ui/skeleton.tsx +1 -1
- package/src/components/ui/switch.tsx +1 -1
- package/src/components/ui/table.tsx +1 -1
- package/src/components/ui/tabs.tsx +1 -1
- package/src/components/ui/tooltip.tsx +1 -1
- package/src/components/ui/vertical-tabs.tsx +1 -1
- package/src/lib/app_logger.ts +1 -1
- package/src/lib/config/config_loader.server.ts +20 -5
- package/src/lib/login_config.server.ts +25 -0
- package/src/lib/register_config.server.ts +17 -1
- package/src/lib/services/login_service.ts +19 -3
- package/src/lib/services/registration_service.ts +25 -4
- package/src/lib/services/user_update_service.ts +16 -3
- package/src/lib/ui_shell_config.server.ts +73 -0
- package/src/lib/utils/error_sanitizer.ts +75 -0
- package/src/stories/email_verification_layout.stories.tsx +2 -2
- package/src/stories/forgot_password_layout.stories.tsx +2 -2
- package/src/stories/login_layout.stories.tsx +2 -2
- package/src/stories/register_layout.stories.tsx +2 -2
- package/tsconfig.json +1 -4
package/README.md
CHANGED
|
@@ -27,7 +27,78 @@ After installing the package, you need to set up configuration files in your pro
|
|
|
27
27
|
- Add `ZEPTOMAIL_API_KEY=your_api_key_here` (if using Zeptomail)
|
|
28
28
|
- Add other sensitive configuration values as needed
|
|
29
29
|
|
|
30
|
-
**Important:** The configuration files must be located in your project root directory (where `process.cwd()` points to), not inside `node_modules`. The package reads configuration from `process.cwd()` at runtime.
|
|
30
|
+
**Important:** The configuration files must be located in your project root directory (where `process.cwd()` points to), not inside `node_modules`. The package reads configuration from `process.cwd()` at runtime, so storing them elsewhere (including `node_modules/hazo_auth`) will break runtime access.
|
|
31
|
+
|
|
32
|
+
### Expose hazo_auth Routes in the Consumer App
|
|
33
|
+
|
|
34
|
+
Because `src/app/hazo_auth` (pages) and `src/app/api/hazo_auth` (API routes) need to be part of the consuming Next.js app’s routing tree, make sure they exist in your project’s `src/app` directory. Two recommended approaches:
|
|
35
|
+
|
|
36
|
+
1. **Create symlinks (preferred during development):**
|
|
37
|
+
```bash
|
|
38
|
+
mkdir -p src/app/api src/app
|
|
39
|
+
ln -s ../../node_modules/hazo_auth/src/app/api/hazo_auth src/app/api/hazo_auth
|
|
40
|
+
ln -s ../../node_modules/hazo_auth/src/app/hazo_auth src/app/hazo_auth
|
|
41
|
+
```
|
|
42
|
+
Adjust the relative paths if your project structure differs.
|
|
43
|
+
|
|
44
|
+
2. **Copy the directories (useful for deployment or when symlinks cause issues):**
|
|
45
|
+
```bash
|
|
46
|
+
cp -R node_modules/hazo_auth/src/app/api/hazo_auth src/app/api/hazo_auth
|
|
47
|
+
cp -R node_modules/hazo_auth/src/app/hazo_auth src/app/hazo_auth
|
|
48
|
+
```
|
|
49
|
+
Add an npm script (e.g., `postinstall`) to automate copying after installations or updates.
|
|
50
|
+
|
|
51
|
+
> The package expects these routes to live at `src/app/api/hazo_auth` and `src/app/hazo_auth` inside the consumer project. Without copying or linking them, Next.js won’t mount the auth pages or APIs.
|
|
52
|
+
|
|
53
|
+
### Choose the UI Shell (Test Sidebar vs Standalone)
|
|
54
|
+
|
|
55
|
+
By default, the pages render inside the “test workspace” sidebar so you can quickly preview every flow. When you reuse the routes inside another project you’ll usually want a clean, standalone wrapper instead. Set this in `hazo_auth_config.ini`:
|
|
56
|
+
|
|
57
|
+
```ini
|
|
58
|
+
[hazo_auth__ui_shell]
|
|
59
|
+
# Options: test_sidebar | standalone
|
|
60
|
+
layout_mode = standalone
|
|
61
|
+
# Optional tweaks for the standalone header wrapper/classes:
|
|
62
|
+
# standalone_heading = Welcome back
|
|
63
|
+
# standalone_description = Your description here
|
|
64
|
+
# standalone_wrapper_class = min-h-screen bg-background py-8
|
|
65
|
+
# standalone_content_class = mx-auto w-full max-w-4xl rounded-2xl border bg-card
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- `test_sidebar`: keeps the developer sidebar (perfect for the demo workspace or Storybook screenshots).
|
|
69
|
+
- `standalone`: renders the page body directly so it inherits your own app shell, layout, and theme tokens.
|
|
70
|
+
- The wrapper and content class overrides let you align spacing/borders with your design system without editing package code.
|
|
71
|
+
|
|
72
|
+
Every route (`/hazo_auth/login`, `/hazo_auth/register`, etc.) automatically looks at this config, so switching modes is instant.
|
|
73
|
+
|
|
74
|
+
### Using Just the Layout Components
|
|
75
|
+
|
|
76
|
+
Prefer to drop the forms into your own routes without copying the provided pages? Import the layouts directly and feed them a `data_client` plus any label/button overrides:
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
// app/(auth)/login/page.tsx in your project
|
|
80
|
+
import login_layout from "hazo_auth/components/layouts/login";
|
|
81
|
+
import { createLayoutDataClient } from "hazo_auth/components/layouts/shared/data/layout_data_client";
|
|
82
|
+
import { create_sqlite_hazo_connect } from "hazo_auth/lib/hazo_connect_setup";
|
|
83
|
+
|
|
84
|
+
export default async function LoginPage() {
|
|
85
|
+
const hazoConnect = create_sqlite_hazo_connect();
|
|
86
|
+
const dataClient = createLayoutDataClient(hazoConnect);
|
|
87
|
+
const LoginLayout = login_layout;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div className="my-app-shell">
|
|
91
|
+
<LoginLayout
|
|
92
|
+
image_src="/marketing/login-hero.svg"
|
|
93
|
+
data_client={dataClient}
|
|
94
|
+
redirectRoute="/dashboard"
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The same import pattern works for every layout under `components/layouts/*`, so you can mix-and-match pieces (profile picture dialog, password field, etc.) wherever you need them.
|
|
31
102
|
|
|
32
103
|
## Authentication Service
|
|
33
104
|
|
|
@@ -7,6 +7,31 @@
|
|
|
7
7
|
# Database type: sqlite, postgrest, supabase, or file
|
|
8
8
|
type = sqlite
|
|
9
9
|
|
|
10
|
+
# UI shell controls whether pages render inside the developer sidebar or a minimal wrapper.
|
|
11
|
+
[hazo_auth__ui_shell]
|
|
12
|
+
# layout_mode = test_sidebar
|
|
13
|
+
# Options:
|
|
14
|
+
# - test_sidebar: keeps the demo sidebar for quick testing in the package workspace.
|
|
15
|
+
# - standalone: renders pages in a minimal wrapper so consumers inherit their own shell/theme.
|
|
16
|
+
#
|
|
17
|
+
# Heading shown above standalone layouts
|
|
18
|
+
# standalone_heading = Welcome to hazo auth
|
|
19
|
+
#
|
|
20
|
+
# Description beneath the heading for standalone layouts
|
|
21
|
+
# standalone_description = Reuse the packaged authentication flows while inheriting your existing app shell styles.
|
|
22
|
+
#
|
|
23
|
+
# Tailwind utility classes applied to the standalone wrapper div
|
|
24
|
+
# standalone_wrapper_class = cls_standalone_shell flex min-h-screen w-full items-center justify-center bg-background px-4 py-10
|
|
25
|
+
#
|
|
26
|
+
# Tailwind utility classes applied to the standalone inner content div
|
|
27
|
+
# standalone_content_class = cls_standalone_shell_content w-full max-w-5xl shadow-xl rounded-2xl border bg-card
|
|
28
|
+
#
|
|
29
|
+
# Show/hide heading in standalone mode (true/false, default: true)
|
|
30
|
+
# standalone_show_heading = true
|
|
31
|
+
#
|
|
32
|
+
# Show/hide description in standalone mode (true/false, default: true)
|
|
33
|
+
# standalone_show_description = true
|
|
34
|
+
|
|
10
35
|
# SQLite database configuration
|
|
11
36
|
# Path to SQLite database file (relative to process.cwd() or absolute)
|
|
12
37
|
sqlite_path = ./data/hazo_auth.sqlite
|
|
@@ -57,6 +82,10 @@ enable_admin_ui = true
|
|
|
57
82
|
# Show name field (true/false)
|
|
58
83
|
# Note: This is now deprecated, use hazo_auth__user_fields section instead
|
|
59
84
|
# show_name_field = false
|
|
85
|
+
#
|
|
86
|
+
# Sign in link shown below register button
|
|
87
|
+
# sign_in_path = /hazo_auth/login
|
|
88
|
+
# sign_in_label = Sign in
|
|
60
89
|
|
|
61
90
|
[hazo_auth__user_fields]
|
|
62
91
|
# Shared user fields configuration used by register and my_settings layouts
|
|
@@ -104,6 +133,12 @@ enable_admin_ui = true
|
|
|
104
133
|
# Redirect on successful login (leave empty to show success message instead)
|
|
105
134
|
# redirect_route_on_successful_login = /
|
|
106
135
|
|
|
136
|
+
# Forgot password + sign up links shown under login button
|
|
137
|
+
# forgot_password_path = /hazo_auth/forgot_password
|
|
138
|
+
# forgot_password_label = Forgot password?
|
|
139
|
+
# create_account_path = /hazo_auth/register
|
|
140
|
+
# create_account_label = Create account
|
|
141
|
+
|
|
107
142
|
# Success message (shown when no redirect route is provided)
|
|
108
143
|
# success_message = Successfully logged in
|
|
109
144
|
|
|
@@ -263,6 +298,12 @@ enable_admin_ui = true
|
|
|
263
298
|
# Example: application_permission_list_defaults = PERM_ONE,PERM_TWO,PERM_THREE
|
|
264
299
|
# application_permission_list_defaults =
|
|
265
300
|
|
|
301
|
+
[hazo_auth__initial_setup]
|
|
302
|
+
# Initial setup configuration for initializing users, roles, and permissions
|
|
303
|
+
# Email address of the super user to assign the default_super_user_role
|
|
304
|
+
# This user will receive all permissions from application_permission_list_defaults
|
|
305
|
+
# default_super_user_email = admin@example.com
|
|
306
|
+
|
|
266
307
|
[hazo_auth__auth_utility]
|
|
267
308
|
# Authentication utility configuration
|
|
268
309
|
|
package/instrumentation.ts
CHANGED
|
@@ -9,13 +9,13 @@ export async function register() {
|
|
|
9
9
|
const hazo_notify_module = await import("hazo_notify");
|
|
10
10
|
|
|
11
11
|
// Step 2: Load hazo_notify emailer configuration
|
|
12
|
-
// This reads from hazo_notify_config.ini in the
|
|
12
|
+
// This reads from hazo_notify_config.ini in the ui_component directory (same location as hazo_auth_config.ini)
|
|
13
13
|
const { load_emailer_config } = hazo_notify_module;
|
|
14
14
|
const notify_config = load_emailer_config();
|
|
15
15
|
|
|
16
16
|
// Step 3: Pass the initialized configuration to hazo_auth email service
|
|
17
17
|
// This allows the email service to reuse the same configuration instance
|
|
18
|
-
const { set_hazo_notify_instance } = await import("
|
|
18
|
+
const { set_hazo_notify_instance } = await import("./src/lib/services/email_service");
|
|
19
19
|
set_hazo_notify_instance(notify_config);
|
|
20
20
|
|
|
21
21
|
// Log successful initialization
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hazo_auth",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"files": [
|
|
5
5
|
"src/**/*",
|
|
6
6
|
"public/file.svg",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"build-storybook": "storybook build",
|
|
32
32
|
"dev:server": "tsx src/server/index.ts",
|
|
33
33
|
"migrate": "tsx scripts/apply_migration.ts",
|
|
34
|
+
"init-users": "tsx scripts/init_users.ts init_users",
|
|
34
35
|
"test": "cross-env NODE_ENV=test POSTGREST_URL=http://209.38.26.241:4402 POSTGREST_API_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYXBpX3VzZXIifQ.zBoUGymrxTUk1DNYIGUCtQU4HFaWEHlbE9_8Y3hUaTw jest --runInBand",
|
|
35
36
|
"test:watch": "cross-env NODE_ENV=test POSTGREST_URL=http://209.38.26.241:4402 POSTGREST_API_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYXBpX3VzZXIifQ.zBoUGymrxTUk1DNYIGUCtQU4HFaWEHlbE9_8Y3hUaTw jest --watch"
|
|
36
37
|
},
|
|
@@ -101,8 +102,8 @@
|
|
|
101
102
|
"@types/react-dom": "^18",
|
|
102
103
|
"better-sqlite3": "^12.4.1",
|
|
103
104
|
"cross-env": "^10.1.0",
|
|
104
|
-
"eslint": "^
|
|
105
|
-
"eslint-config-next": "^
|
|
105
|
+
"eslint": "^8.57.0",
|
|
106
|
+
"eslint-config-next": "^14.2.7",
|
|
106
107
|
"eslint-plugin-storybook": "^10.0.6",
|
|
107
108
|
"jest": "^30.2.0",
|
|
108
109
|
"jest-environment-jsdom": "^29.7.0",
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
// file_description: script to initialize users, roles, and permissions from configuration
|
|
2
|
+
// Run with: npx tsx scripts/init_users.ts init_users
|
|
3
|
+
// section: imports
|
|
4
|
+
import { get_hazo_connect_instance } from "../src/lib/hazo_connect_instance.server";
|
|
5
|
+
import { createCrudService } from "hazo_connect/server";
|
|
6
|
+
import { get_user_management_config } from "../src/lib/user_management_config.server";
|
|
7
|
+
import { get_config_value } from "../src/lib/config/config_loader.server";
|
|
8
|
+
import { create_app_logger } from "../src/lib/app_logger";
|
|
9
|
+
|
|
10
|
+
// section: types
|
|
11
|
+
type InitSummary = {
|
|
12
|
+
permissions: {
|
|
13
|
+
inserted: string[];
|
|
14
|
+
existing: string[];
|
|
15
|
+
};
|
|
16
|
+
role: {
|
|
17
|
+
inserted: boolean;
|
|
18
|
+
existing: boolean;
|
|
19
|
+
role_id: number | null;
|
|
20
|
+
};
|
|
21
|
+
role_permissions: {
|
|
22
|
+
inserted: number;
|
|
23
|
+
existing: number;
|
|
24
|
+
};
|
|
25
|
+
user_role: {
|
|
26
|
+
inserted: boolean;
|
|
27
|
+
existing: boolean;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// section: helpers
|
|
32
|
+
/**
|
|
33
|
+
* Displays help information for available commands
|
|
34
|
+
*/
|
|
35
|
+
function show_help(): void {
|
|
36
|
+
console.log(`
|
|
37
|
+
hazo_auth CLI - User and Permission Management
|
|
38
|
+
|
|
39
|
+
Usage: npx tsx scripts/init_users.ts <command>
|
|
40
|
+
|
|
41
|
+
Available Commands:
|
|
42
|
+
init_users Initialize users, roles, and permissions from configuration
|
|
43
|
+
- Reads permissions from hazo_auth_config.ini [hazo_auth__user_management] application_permission_list_defaults
|
|
44
|
+
- Creates default_super_user_role in hazo_roles
|
|
45
|
+
- Assigns all permissions to the super user role
|
|
46
|
+
- Finds user by email from hazo_auth_config.ini [hazo_auth__initial_setup] default_super_user_email
|
|
47
|
+
- Assigns super user role to the user
|
|
48
|
+
- Provides summary of what was inserted vs what already existed
|
|
49
|
+
|
|
50
|
+
help Show this help message
|
|
51
|
+
|
|
52
|
+
Configuration:
|
|
53
|
+
Add the following to hazo_auth_config.ini:
|
|
54
|
+
|
|
55
|
+
[hazo_auth__user_management]
|
|
56
|
+
application_permission_list_defaults = admin_user_management,admin_role_management,admin_permission_management
|
|
57
|
+
|
|
58
|
+
[hazo_auth__initial_setup]
|
|
59
|
+
default_super_user_email = admin@example.com
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
npx tsx scripts/init_users.ts init_users
|
|
63
|
+
npx tsx scripts/init_users.ts help
|
|
64
|
+
`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Initializes users, roles, and permissions from configuration
|
|
69
|
+
*/
|
|
70
|
+
async function init_users(): Promise<void> {
|
|
71
|
+
const logger = create_app_logger();
|
|
72
|
+
const summary: InitSummary = {
|
|
73
|
+
permissions: {
|
|
74
|
+
inserted: [],
|
|
75
|
+
existing: [],
|
|
76
|
+
},
|
|
77
|
+
role: {
|
|
78
|
+
inserted: false,
|
|
79
|
+
existing: false,
|
|
80
|
+
role_id: null,
|
|
81
|
+
},
|
|
82
|
+
role_permissions: {
|
|
83
|
+
inserted: 0,
|
|
84
|
+
existing: 0,
|
|
85
|
+
},
|
|
86
|
+
user_role: {
|
|
87
|
+
inserted: false,
|
|
88
|
+
existing: false,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
console.log("Initializing users, roles, and permissions from configuration...\n");
|
|
94
|
+
|
|
95
|
+
// Get hazo_connect instance
|
|
96
|
+
const hazoConnect = get_hazo_connect_instance();
|
|
97
|
+
const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
|
|
98
|
+
const roles_service = createCrudService(hazoConnect, "hazo_roles");
|
|
99
|
+
const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions");
|
|
100
|
+
const users_service = createCrudService(hazoConnect, "hazo_users");
|
|
101
|
+
const user_roles_service = createCrudService(hazoConnect, "hazo_user_roles");
|
|
102
|
+
|
|
103
|
+
// 1. Get permissions from config
|
|
104
|
+
const config = get_user_management_config();
|
|
105
|
+
const permission_names = config.application_permission_list_defaults || [];
|
|
106
|
+
|
|
107
|
+
if (permission_names.length === 0) {
|
|
108
|
+
console.log("⚠ No permissions found in configuration.");
|
|
109
|
+
console.log(" Add permissions to [hazo_auth__user_management] application_permission_list_defaults\n");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log(`Found ${permission_names.length} permission(s) in configuration:`);
|
|
114
|
+
permission_names.forEach((name) => console.log(` - ${name}`));
|
|
115
|
+
console.log();
|
|
116
|
+
|
|
117
|
+
// 2. Add permissions to hazo_permissions table
|
|
118
|
+
const permission_id_map: Record<string, number> = {};
|
|
119
|
+
const now = new Date().toISOString();
|
|
120
|
+
|
|
121
|
+
for (const permission_name of permission_names) {
|
|
122
|
+
const trimmed_name = permission_name.trim();
|
|
123
|
+
if (!trimmed_name) continue;
|
|
124
|
+
|
|
125
|
+
// Check if permission already exists
|
|
126
|
+
const existing_permissions = await permissions_service.findBy({
|
|
127
|
+
permission_name: trimmed_name,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (Array.isArray(existing_permissions) && existing_permissions.length > 0) {
|
|
131
|
+
const existing_permission = existing_permissions[0];
|
|
132
|
+
const perm_id = existing_permission.id as number;
|
|
133
|
+
permission_id_map[trimmed_name] = perm_id;
|
|
134
|
+
summary.permissions.existing.push(trimmed_name);
|
|
135
|
+
console.log(`✓ Permission already exists: ${trimmed_name} (ID: ${perm_id})`);
|
|
136
|
+
} else {
|
|
137
|
+
// Insert new permission
|
|
138
|
+
const new_permission = await permissions_service.insert({
|
|
139
|
+
permission_name: trimmed_name,
|
|
140
|
+
description: `Permission for ${trimmed_name}`,
|
|
141
|
+
created_at: now,
|
|
142
|
+
changed_at: now,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const perm_id = Array.isArray(new_permission)
|
|
146
|
+
? (new_permission[0] as { id: number }).id
|
|
147
|
+
: (new_permission as { id: number }).id;
|
|
148
|
+
permission_id_map[trimmed_name] = perm_id;
|
|
149
|
+
summary.permissions.inserted.push(trimmed_name);
|
|
150
|
+
console.log(`✓ Inserted permission: ${trimmed_name} (ID: ${perm_id})`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log();
|
|
155
|
+
|
|
156
|
+
// 3. Create or get default_super_user_role
|
|
157
|
+
const role_name = "default_super_user_role";
|
|
158
|
+
const existing_roles = await roles_service.findBy({
|
|
159
|
+
role_name,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
let role_id: number;
|
|
163
|
+
if (Array.isArray(existing_roles) && existing_roles.length > 0) {
|
|
164
|
+
role_id = existing_roles[0].id as number;
|
|
165
|
+
summary.role.existing = true;
|
|
166
|
+
summary.role.role_id = role_id;
|
|
167
|
+
console.log(`✓ Role already exists: ${role_name} (ID: ${role_id})`);
|
|
168
|
+
} else {
|
|
169
|
+
const new_role = await roles_service.insert({
|
|
170
|
+
role_name,
|
|
171
|
+
created_at: now,
|
|
172
|
+
changed_at: now,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
role_id = Array.isArray(new_role)
|
|
176
|
+
? (new_role[0] as { id: number }).id
|
|
177
|
+
: (new_role as { id: number }).id;
|
|
178
|
+
summary.role.inserted = true;
|
|
179
|
+
summary.role.role_id = role_id;
|
|
180
|
+
console.log(`✓ Created role: ${role_name} (ID: ${role_id})`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
console.log();
|
|
184
|
+
|
|
185
|
+
// 4. Assign all permissions to the role
|
|
186
|
+
const permission_ids = Object.values(permission_id_map);
|
|
187
|
+
|
|
188
|
+
for (const permission_id of permission_ids) {
|
|
189
|
+
// Check if role-permission assignment already exists
|
|
190
|
+
const existing_assignments = await role_permissions_service.findBy({
|
|
191
|
+
role_id,
|
|
192
|
+
permission_id,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (Array.isArray(existing_assignments) && existing_assignments.length > 0) {
|
|
196
|
+
summary.role_permissions.existing++;
|
|
197
|
+
const perm_name = Object.keys(permission_id_map).find(
|
|
198
|
+
(key) => permission_id_map[key] === permission_id,
|
|
199
|
+
);
|
|
200
|
+
console.log(`✓ Role-permission already exists: ${role_name} -> ${perm_name}`);
|
|
201
|
+
} else {
|
|
202
|
+
await role_permissions_service.insert({
|
|
203
|
+
role_id,
|
|
204
|
+
permission_id,
|
|
205
|
+
created_at: now,
|
|
206
|
+
changed_at: now,
|
|
207
|
+
});
|
|
208
|
+
summary.role_permissions.inserted++;
|
|
209
|
+
const perm_name = Object.keys(permission_id_map).find(
|
|
210
|
+
(key) => permission_id_map[key] === permission_id,
|
|
211
|
+
);
|
|
212
|
+
console.log(`✓ Assigned permission to role: ${role_name} -> ${perm_name}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
console.log();
|
|
217
|
+
|
|
218
|
+
// 5. Get super user email from config
|
|
219
|
+
const super_user_email = get_config_value(
|
|
220
|
+
"hazo_auth__initial_setup",
|
|
221
|
+
"default_super_user_email",
|
|
222
|
+
"",
|
|
223
|
+
).trim();
|
|
224
|
+
|
|
225
|
+
if (!super_user_email) {
|
|
226
|
+
console.log("⚠ No super user email found in configuration.");
|
|
227
|
+
console.log(" Add [hazo_auth__initial_setup] default_super_user_email to config\n");
|
|
228
|
+
print_summary(summary);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
console.log(`Looking up user with email: ${super_user_email}`);
|
|
233
|
+
|
|
234
|
+
// 6. Find user by email
|
|
235
|
+
const users = await users_service.findBy({
|
|
236
|
+
email_address: super_user_email,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
if (!Array.isArray(users) || users.length === 0) {
|
|
240
|
+
console.log(`✗ User not found with email: ${super_user_email}`);
|
|
241
|
+
console.log(" Please ensure the user exists in the database before running this script.\n");
|
|
242
|
+
print_summary(summary);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const user = users[0];
|
|
247
|
+
const user_id = user.id as string;
|
|
248
|
+
console.log(`✓ Found user: ${super_user_email} (ID: ${user_id})`);
|
|
249
|
+
console.log();
|
|
250
|
+
|
|
251
|
+
// 7. Assign role to user
|
|
252
|
+
const existing_user_roles = await user_roles_service.findBy({
|
|
253
|
+
user_id,
|
|
254
|
+
role_id,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (Array.isArray(existing_user_roles) && existing_user_roles.length > 0) {
|
|
258
|
+
summary.user_role.existing = true;
|
|
259
|
+
console.log(`✓ User already has role assigned: ${user_id} -> ${role_name}`);
|
|
260
|
+
} else {
|
|
261
|
+
await user_roles_service.insert({
|
|
262
|
+
user_id,
|
|
263
|
+
role_id,
|
|
264
|
+
created_at: now,
|
|
265
|
+
changed_at: now,
|
|
266
|
+
});
|
|
267
|
+
summary.user_role.inserted = true;
|
|
268
|
+
console.log(`✓ Assigned role to user: ${user_id} -> ${role_name}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
console.log();
|
|
272
|
+
|
|
273
|
+
// 8. Print summary
|
|
274
|
+
print_summary(summary);
|
|
275
|
+
|
|
276
|
+
logger.info("init_users_completed", {
|
|
277
|
+
filename: "init_users.ts",
|
|
278
|
+
line_number: 0,
|
|
279
|
+
summary,
|
|
280
|
+
});
|
|
281
|
+
} catch (error) {
|
|
282
|
+
const error_message = error instanceof Error ? error.message : "Unknown error";
|
|
283
|
+
const error_stack = error instanceof Error ? error.stack : undefined;
|
|
284
|
+
|
|
285
|
+
console.error("\n✗ Error initializing users:");
|
|
286
|
+
console.error(` ${error_message}`);
|
|
287
|
+
if (error_stack) {
|
|
288
|
+
console.error("\nStack trace:");
|
|
289
|
+
console.error(error_stack);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const logger = create_app_logger();
|
|
293
|
+
logger.error("init_users_failed", {
|
|
294
|
+
filename: "init_users.ts",
|
|
295
|
+
line_number: 0,
|
|
296
|
+
error_message,
|
|
297
|
+
error_stack,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Prints a summary of what was inserted vs what already existed
|
|
306
|
+
*/
|
|
307
|
+
function print_summary(summary: InitSummary): void {
|
|
308
|
+
console.log("=".repeat(60));
|
|
309
|
+
console.log("INITIALIZATION SUMMARY");
|
|
310
|
+
console.log("=".repeat(60));
|
|
311
|
+
console.log();
|
|
312
|
+
|
|
313
|
+
// Permissions summary
|
|
314
|
+
console.log("Permissions:");
|
|
315
|
+
if (summary.permissions.inserted.length > 0) {
|
|
316
|
+
console.log(` ✓ Inserted (${summary.permissions.inserted.length}):`);
|
|
317
|
+
summary.permissions.inserted.forEach((name) => console.log(` - ${name}`));
|
|
318
|
+
}
|
|
319
|
+
if (summary.permissions.existing.length > 0) {
|
|
320
|
+
console.log(` ⊙ Already existed (${summary.permissions.existing.length}):`);
|
|
321
|
+
summary.permissions.existing.forEach((name) => console.log(` - ${name}`));
|
|
322
|
+
}
|
|
323
|
+
console.log();
|
|
324
|
+
|
|
325
|
+
// Role summary
|
|
326
|
+
console.log("Role:");
|
|
327
|
+
if (summary.role.inserted) {
|
|
328
|
+
console.log(` ✓ Inserted: default_super_user_role (ID: ${summary.role.role_id})`);
|
|
329
|
+
}
|
|
330
|
+
if (summary.role.existing) {
|
|
331
|
+
console.log(` ⊙ Already existed: default_super_user_role (ID: ${summary.role.role_id})`);
|
|
332
|
+
}
|
|
333
|
+
console.log();
|
|
334
|
+
|
|
335
|
+
// Role permissions summary
|
|
336
|
+
console.log("Role-Permission Assignments:");
|
|
337
|
+
if (summary.role_permissions.inserted > 0) {
|
|
338
|
+
console.log(` ✓ Inserted: ${summary.role_permissions.inserted} assignment(s)`);
|
|
339
|
+
}
|
|
340
|
+
if (summary.role_permissions.existing > 0) {
|
|
341
|
+
console.log(` ⊙ Already existed: ${summary.role_permissions.existing} assignment(s)`);
|
|
342
|
+
}
|
|
343
|
+
console.log();
|
|
344
|
+
|
|
345
|
+
// User role summary
|
|
346
|
+
console.log("User-Role Assignment:");
|
|
347
|
+
if (summary.user_role.inserted) {
|
|
348
|
+
console.log(` ✓ Inserted: Super user role assigned to user`);
|
|
349
|
+
}
|
|
350
|
+
if (summary.user_role.existing) {
|
|
351
|
+
console.log(` ⊙ Already existed: User already has super user role`);
|
|
352
|
+
}
|
|
353
|
+
console.log();
|
|
354
|
+
|
|
355
|
+
console.log("=".repeat(60));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// section: main
|
|
359
|
+
function main(): void {
|
|
360
|
+
const command = process.argv[2];
|
|
361
|
+
|
|
362
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
363
|
+
show_help();
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (command === "init_users") {
|
|
368
|
+
void init_users();
|
|
369
|
+
} else {
|
|
370
|
+
console.error(`Unknown command: ${command}\n`);
|
|
371
|
+
show_help();
|
|
372
|
+
process.exit(1);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
main();
|
|
377
|
+
|
|
378
|
+
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// file_description: API route for uploading profile pictures
|
|
2
2
|
// section: imports
|
|
3
3
|
import { NextRequest, NextResponse } from "next/server";
|
|
4
|
-
import { get_hazo_connect_instance } from "
|
|
5
|
-
import { create_app_logger } from "
|
|
6
|
-
import { get_profile_picture_config } from "
|
|
7
|
-
import { get_file_types_config } from "
|
|
8
|
-
import { update_user_profile_picture } from "
|
|
4
|
+
import { get_hazo_connect_instance } from "../../../../../lib/hazo_connect_instance.server";
|
|
5
|
+
import { create_app_logger } from "../../../../../lib/app_logger";
|
|
6
|
+
import { get_profile_picture_config } from "../../../../../lib/profile_picture_config.server";
|
|
7
|
+
import { get_file_types_config } from "../../../../../lib/file_types_config.server";
|
|
8
|
+
import { update_user_profile_picture } from "../../../../../lib/services/profile_picture_service";
|
|
9
9
|
import { createCrudService } from "hazo_connect/server";
|
|
10
|
-
import { map_db_source_to_ui } from "
|
|
11
|
-
import { get_filename, get_line_number } from "
|
|
10
|
+
import { map_db_source_to_ui } from "../../../../../lib/services/profile_picture_source_mapper";
|
|
11
|
+
import { get_filename, get_line_number } from "../../../../../lib/utils/api_route_helpers";
|
|
12
12
|
import fs from "fs";
|
|
13
13
|
import path from "path";
|
|
14
14
|
|
|
@@ -20,7 +20,7 @@ export async function POST(request: NextRequest) {
|
|
|
20
20
|
// Use centralized auth check
|
|
21
21
|
let user_id: string;
|
|
22
22
|
try {
|
|
23
|
-
const { require_auth } = await import("
|
|
23
|
+
const { require_auth } = await import("../../../../../lib/auth/auth_utils.server");
|
|
24
24
|
const user = await require_auth(request);
|
|
25
25
|
user_id = user.user_id;
|
|
26
26
|
} catch (error) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// file_description: API route for changing user password
|
|
2
2
|
// section: imports
|
|
3
3
|
import { NextRequest, NextResponse } from "next/server";
|
|
4
|
-
import { get_hazo_connect_instance } from "
|
|
5
|
-
import { create_app_logger } from "
|
|
6
|
-
import { change_password } from "
|
|
7
|
-
import { get_filename, get_line_number } from "
|
|
8
|
-
import { require_auth } from "
|
|
9
|
-
import { get_auth_cache } from "
|
|
10
|
-
import { get_auth_utility_config } from "
|
|
4
|
+
import { get_hazo_connect_instance } from "../../../../lib/hazo_connect_instance.server";
|
|
5
|
+
import { create_app_logger } from "../../../../lib/app_logger";
|
|
6
|
+
import { change_password } from "../../../../lib/services/password_change_service";
|
|
7
|
+
import { get_filename, get_line_number } from "../../../../lib/utils/api_route_helpers";
|
|
8
|
+
import { require_auth } from "../../../../lib/auth/auth_utils.server";
|
|
9
|
+
import { get_auth_cache } from "../../../../lib/auth/auth_cache";
|
|
10
|
+
import { get_auth_utility_config } from "../../../../lib/auth_utility_config.server";
|
|
11
11
|
|
|
12
12
|
// section: api_handler
|
|
13
13
|
export async function POST(request: NextRequest) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// file_description: API route for password reset requests using hazo_connect
|
|
2
2
|
// section: imports
|
|
3
3
|
import { NextRequest, NextResponse } from "next/server";
|
|
4
|
-
import { get_hazo_connect_instance } from "
|
|
5
|
-
import { create_app_logger } from "
|
|
6
|
-
import { request_password_reset } from "
|
|
7
|
-
import { get_filename, get_line_number } from "
|
|
4
|
+
import { get_hazo_connect_instance } from "../../../../lib/hazo_connect_instance.server";
|
|
5
|
+
import { create_app_logger } from "../../../../lib/app_logger";
|
|
6
|
+
import { request_password_reset } from "../../../../lib/services/password_reset_service";
|
|
7
|
+
import { get_filename, get_line_number } from "../../../../lib/utils/api_route_helpers";
|
|
8
8
|
|
|
9
9
|
// section: api_handler
|
|
10
10
|
export async function POST(request: NextRequest) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// file_description: API route for hazo_get_auth utility (client-side calls)
|
|
2
2
|
// section: imports
|
|
3
3
|
import { NextRequest, NextResponse } from "next/server";
|
|
4
|
-
import { hazo_get_auth } from "
|
|
5
|
-
import { PermissionError } from "
|
|
6
|
-
import { create_app_logger } from "
|
|
7
|
-
import { get_filename, get_line_number } from "
|
|
4
|
+
import { hazo_get_auth } from "../../../../lib/auth/hazo_get_auth.server";
|
|
5
|
+
import { PermissionError } from "../../../../lib/auth/auth_types";
|
|
6
|
+
import { create_app_logger } from "../../../../lib/app_logger";
|
|
7
|
+
import { get_filename, get_line_number } from "../../../../lib/utils/api_route_helpers";
|
|
8
8
|
|
|
9
9
|
// section: route_config
|
|
10
10
|
export const dynamic = "force-dynamic";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// file_description: API route for manual cache invalidation (admin endpoint)
|
|
2
2
|
// section: imports
|
|
3
3
|
import { NextRequest, NextResponse } from "next/server";
|
|
4
|
-
import { get_auth_cache } from "
|
|
5
|
-
import { get_auth_utility_config } from "
|
|
6
|
-
import { create_app_logger } from "
|
|
7
|
-
import { get_filename, get_line_number } from "
|
|
8
|
-
import { hazo_get_auth } from "
|
|
4
|
+
import { get_auth_cache } from "../../../../lib/auth/auth_cache";
|
|
5
|
+
import { get_auth_utility_config } from "../../../../lib/auth_utility_config.server";
|
|
6
|
+
import { create_app_logger } from "../../../../lib/app_logger";
|
|
7
|
+
import { get_filename, get_line_number } from "../../../../lib/utils/api_route_helpers";
|
|
8
|
+
import { hazo_get_auth } from "../../../../lib/auth/hazo_get_auth.server";
|
|
9
9
|
|
|
10
10
|
// section: route_config
|
|
11
11
|
export const dynamic = "force-dynamic";
|