hazo_auth 4.3.0 → 4.4.1
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/cli-src/lib/already_logged_in_config.server.ts +1 -1
- package/cli-src/lib/app_logger.ts +8 -18
- package/cli-src/lib/auth/auth_types.ts +7 -0
- package/cli-src/lib/auth/auth_utils.server.ts +2 -2
- package/cli-src/lib/auth/dev_lock_validator.edge.ts +171 -0
- package/cli-src/lib/auth/hazo_get_auth.server.ts +84 -13
- package/cli-src/lib/auth/index.ts +5 -5
- package/cli-src/lib/auth/nextauth_config.ts +4 -4
- package/cli-src/lib/auth/org_cache.ts +148 -0
- package/cli-src/lib/auth/server_auth.ts +2 -2
- package/cli-src/lib/auth/session_token_validator.edge.ts +4 -0
- package/cli-src/lib/auth_utility_config.server.ts +1 -1
- package/cli-src/lib/config/config_loader.server.ts +1 -1
- package/cli-src/lib/config/default_config.ts +44 -0
- package/cli-src/lib/dev_lock_config.server.ts +148 -0
- package/cli-src/lib/email_verification_config.server.ts +3 -3
- package/cli-src/lib/file_types_config.server.ts +1 -1
- package/cli-src/lib/forgot_password_config.server.ts +3 -3
- package/cli-src/lib/hazo_connect_instance.server.ts +2 -2
- package/cli-src/lib/hazo_connect_setup.server.ts +2 -2
- package/cli-src/lib/index.ts +24 -24
- package/cli-src/lib/login_config.server.ts +4 -4
- package/cli-src/lib/messages_config.server.ts +1 -1
- package/cli-src/lib/multi_tenancy_config.server.ts +94 -0
- package/cli-src/lib/my_settings_config.server.ts +7 -7
- package/cli-src/lib/oauth_config.server.ts +2 -2
- package/cli-src/lib/password_requirements_config.server.ts +2 -2
- package/cli-src/lib/profile_pic_menu_config.server.ts +1 -1
- package/cli-src/lib/profile_picture_config.server.ts +2 -2
- package/cli-src/lib/register_config.server.ts +5 -5
- package/cli-src/lib/reset_password_config.server.ts +4 -4
- package/cli-src/lib/scope_hierarchy_config.server.ts +2 -2
- package/cli-src/lib/services/email_service.ts +2 -2
- package/cli-src/lib/services/email_verification_service.ts +3 -3
- package/cli-src/lib/services/login_service.ts +3 -3
- package/cli-src/lib/services/oauth_service.ts +4 -4
- package/cli-src/lib/services/org_service.ts +965 -0
- package/cli-src/lib/services/password_change_service.ts +3 -3
- package/cli-src/lib/services/password_reset_service.ts +3 -3
- package/cli-src/lib/services/profile_picture_remove_service.ts +3 -3
- package/cli-src/lib/services/profile_picture_service.ts +5 -5
- package/cli-src/lib/services/registration_service.ts +8 -8
- package/cli-src/lib/services/scope_labels_service.ts +3 -3
- package/cli-src/lib/services/scope_service.ts +2 -2
- package/cli-src/lib/services/session_token_service.ts +6 -2
- package/cli-src/lib/services/token_service.ts +2 -2
- package/cli-src/lib/services/user_profiles_service.ts +4 -4
- package/cli-src/lib/services/user_scope_service.ts +3 -3
- package/cli-src/lib/services/user_update_service.ts +4 -4
- package/cli-src/lib/ui_shell_config.server.ts +1 -1
- package/cli-src/lib/ui_sizes_config.server.ts +1 -1
- package/cli-src/lib/user_fields_config.server.ts +1 -1
- package/cli-src/lib/user_management_config.server.ts +1 -1
- package/cli-src/lib/user_profiles_config.server.ts +1 -1
- package/cli-src/lib/utils/error_sanitizer.ts +1 -1
- package/cli-src/server/types/app_types.ts +72 -0
- package/cli-src/server/types/express.d.ts +16 -0
- package/dist/components/layouts/dev_lock/index.d.ts +29 -0
- package/dist/components/layouts/dev_lock/index.d.ts.map +1 -0
- package/dist/components/layouts/dev_lock/index.js +60 -0
- package/dist/components/layouts/index.d.ts +2 -0
- package/dist/components/layouts/index.d.ts.map +1 -1
- package/dist/components/layouts/index.js +1 -0
- package/dist/components/layouts/login/hooks/use_login_form.js +2 -2
- package/dist/components/layouts/org_management/index.d.ts +26 -0
- package/dist/components/layouts/org_management/index.d.ts.map +1 -0
- package/dist/components/layouts/org_management/index.js +75 -0
- package/dist/components/layouts/user_management/components/org_hierarchy_tab.d.ts +13 -0
- package/dist/components/layouts/user_management/components/org_hierarchy_tab.d.ts.map +1 -0
- package/dist/components/layouts/user_management/components/org_hierarchy_tab.js +276 -0
- package/dist/components/layouts/user_management/index.d.ts +3 -1
- package/dist/components/layouts/user_management/index.d.ts.map +1 -1
- package/dist/components/layouts/user_management/index.js +10 -4
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/lib/app_logger.d.ts +3 -9
- package/dist/lib/app_logger.d.ts.map +1 -1
- package/dist/lib/app_logger.js +7 -10
- package/dist/lib/auth/auth_types.d.ts +6 -0
- package/dist/lib/auth/auth_types.d.ts.map +1 -1
- package/dist/lib/auth/dev_lock_validator.edge.d.ts +38 -0
- package/dist/lib/auth/dev_lock_validator.edge.d.ts.map +1 -0
- package/dist/lib/auth/dev_lock_validator.edge.js +122 -0
- package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_auth.server.js +61 -1
- package/dist/lib/auth/org_cache.d.ts +65 -0
- package/dist/lib/auth/org_cache.d.ts.map +1 -0
- package/dist/lib/auth/org_cache.js +103 -0
- package/dist/lib/config/default_config.d.ts +76 -0
- package/dist/lib/config/default_config.d.ts.map +1 -1
- package/dist/lib/config/default_config.js +42 -0
- package/dist/lib/dev_lock_config.server.d.ts +41 -0
- package/dist/lib/dev_lock_config.server.d.ts.map +1 -0
- package/dist/lib/dev_lock_config.server.js +50 -0
- package/dist/lib/multi_tenancy_config.server.d.ts +30 -0
- package/dist/lib/multi_tenancy_config.server.d.ts.map +1 -0
- package/dist/lib/multi_tenancy_config.server.js +41 -0
- package/dist/lib/services/org_service.d.ts +191 -0
- package/dist/lib/services/org_service.d.ts.map +1 -0
- package/dist/lib/services/org_service.js +746 -0
- package/dist/page_components/dev_lock.d.ts +11 -0
- package/dist/page_components/dev_lock.d.ts.map +1 -0
- package/dist/page_components/dev_lock.js +17 -0
- package/dist/page_components/index.d.ts +1 -0
- package/dist/page_components/index.d.ts.map +1 -1
- package/dist/page_components/index.js +1 -0
- package/dist/page_components/login.d.ts.map +1 -1
- package/dist/page_components/login.js +3 -7
- package/dist/page_components/org_management.d.ts +27 -0
- package/dist/page_components/org_management.d.ts.map +1 -0
- package/dist/page_components/org_management.js +18 -0
- package/dist/server/config/config_loader.js +2 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -3
- package/dist/server/types/app_types.d.ts +3 -7
- package/dist/server/types/app_types.d.ts.map +1 -1
- package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/login_client_wrapper.js +1 -3
- package/hazo_auth_config.example.ini +30 -0
- package/package.json +29 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// file_description: server-only helper to read already logged in configuration from hazo_auth_config.ini
|
|
2
2
|
// section: imports
|
|
3
|
-
import { get_config_value, get_config_boolean } from "./config/config_loader.server";
|
|
3
|
+
import { get_config_value, get_config_boolean } from "./config/config_loader.server.js";
|
|
4
4
|
|
|
5
5
|
// section: types
|
|
6
6
|
export type AlreadyLoggedInConfig = {
|
|
@@ -1,24 +1,14 @@
|
|
|
1
|
-
// file_description: client-accessible wrapper for the main app logging service
|
|
1
|
+
// file_description: client-accessible wrapper for the main app logging service using hazo_logs
|
|
2
2
|
// section: imports
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
// section: constants
|
|
6
|
-
const APP_NAMESPACE = "hazo_auth_ui";
|
|
3
|
+
import { createLogger } from "hazo_logs";
|
|
7
4
|
|
|
8
5
|
// section: logger_instance
|
|
6
|
+
// Create a singleton logger for the hazo_auth package
|
|
7
|
+
const logger = createLogger("hazo_auth");
|
|
8
|
+
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* when provided as part of component setup
|
|
10
|
+
* Returns the hazo_auth logger instance
|
|
11
|
+
* Uses hazo_logs for consistent logging across hazo packages
|
|
13
12
|
*/
|
|
14
|
-
export const create_app_logger = (
|
|
15
|
-
external_logger?: {
|
|
16
|
-
info?: (message: string, data?: Record<string, unknown>) => void;
|
|
17
|
-
error?: (message: string, data?: Record<string, unknown>) => void;
|
|
18
|
-
warn?: (message: string, data?: Record<string, unknown>) => void;
|
|
19
|
-
debug?: (message: string, data?: Record<string, unknown>) => void;
|
|
20
|
-
}
|
|
21
|
-
) => {
|
|
22
|
-
return create_logger_service(APP_NAMESPACE, external_logger);
|
|
23
|
-
};
|
|
13
|
+
export const create_app_logger = () => logger;
|
|
24
14
|
|
|
@@ -10,6 +10,13 @@ export type HazoAuthUser = {
|
|
|
10
10
|
email_address: string;
|
|
11
11
|
is_active: boolean;
|
|
12
12
|
profile_picture_url: string | null;
|
|
13
|
+
// Multi-tenancy fields (only populated when multi-tenancy is enabled)
|
|
14
|
+
org_id?: string | null;
|
|
15
|
+
org_name?: string | null;
|
|
16
|
+
parent_org_id?: string | null;
|
|
17
|
+
parent_org_name?: string | null;
|
|
18
|
+
root_org_id?: string | null;
|
|
19
|
+
root_org_name?: string | null;
|
|
13
20
|
};
|
|
14
21
|
|
|
15
22
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// file_description: server-side authentication utilities for checking login status in API routes
|
|
2
2
|
// section: imports
|
|
3
3
|
import { NextRequest, NextResponse } from "next/server";
|
|
4
|
-
import { get_hazo_connect_instance } from "../hazo_connect_instance.server";
|
|
4
|
+
import { get_hazo_connect_instance } from "../hazo_connect_instance.server.js";
|
|
5
5
|
import { createCrudService } from "hazo_connect/server";
|
|
6
|
-
import { map_db_source_to_ui } from "../services/profile_picture_source_mapper";
|
|
6
|
+
import { map_db_source_to_ui } from "../services/profile_picture_source_mapper.js";
|
|
7
7
|
|
|
8
8
|
// section: types
|
|
9
9
|
export type AuthUser = {
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// file_description: Edge-compatible dev lock cookie validator for Next.js middleware
|
|
2
|
+
// Uses Web Crypto API which works in Edge Runtime (no Node.js crypto module)
|
|
3
|
+
// section: imports
|
|
4
|
+
import type { NextRequest } from "next/server";
|
|
5
|
+
|
|
6
|
+
// section: constants
|
|
7
|
+
const COOKIE_NAME = "hazo_auth_dev_lock";
|
|
8
|
+
const SEPARATOR = "|";
|
|
9
|
+
|
|
10
|
+
// section: types
|
|
11
|
+
export type DevLockValidationResult = {
|
|
12
|
+
valid: boolean;
|
|
13
|
+
expired?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type DevLockCookieData = {
|
|
17
|
+
value: string;
|
|
18
|
+
max_age: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// section: helpers
|
|
22
|
+
/**
|
|
23
|
+
* Creates HMAC-SHA256 signature using Web Crypto API (Edge compatible)
|
|
24
|
+
* @param data - Data to sign
|
|
25
|
+
* @param secret - Secret key for signing
|
|
26
|
+
* @returns Hex string signature
|
|
27
|
+
*/
|
|
28
|
+
async function create_signature(data: string, secret: string): Promise<string> {
|
|
29
|
+
const encoder = new TextEncoder();
|
|
30
|
+
const key_data = encoder.encode(secret);
|
|
31
|
+
const message_data = encoder.encode(data);
|
|
32
|
+
|
|
33
|
+
const crypto_key = await crypto.subtle.importKey(
|
|
34
|
+
"raw",
|
|
35
|
+
key_data,
|
|
36
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
37
|
+
false,
|
|
38
|
+
["sign"]
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const signature = await crypto.subtle.sign("HMAC", crypto_key, message_data);
|
|
42
|
+
|
|
43
|
+
// Convert ArrayBuffer to hex string
|
|
44
|
+
return Array.from(new Uint8Array(signature))
|
|
45
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
46
|
+
.join("");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Performs constant-time comparison of two strings
|
|
51
|
+
* Prevents timing attacks
|
|
52
|
+
* @param a - First string
|
|
53
|
+
* @param b - Second string
|
|
54
|
+
* @returns true if strings are equal
|
|
55
|
+
*/
|
|
56
|
+
function constant_time_compare(a: string, b: string): boolean {
|
|
57
|
+
if (a.length !== b.length) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let result = 0;
|
|
62
|
+
for (let i = 0; i < a.length; i++) {
|
|
63
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return result === 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// section: main_functions
|
|
70
|
+
/**
|
|
71
|
+
* Creates a signed dev lock cookie value
|
|
72
|
+
* Cookie format: timestamp|expiry_timestamp|signature
|
|
73
|
+
* @param password - The dev lock password (used as signing key)
|
|
74
|
+
* @param expiry_days - Number of days until cookie expires (default: 7)
|
|
75
|
+
* @returns Cookie value and max_age in seconds
|
|
76
|
+
*/
|
|
77
|
+
export async function create_dev_lock_cookie(
|
|
78
|
+
password: string,
|
|
79
|
+
expiry_days: number = 7
|
|
80
|
+
): Promise<DevLockCookieData> {
|
|
81
|
+
const timestamp = Date.now();
|
|
82
|
+
const expiry_timestamp = timestamp + expiry_days * 24 * 60 * 60 * 1000;
|
|
83
|
+
const data = `${timestamp}${SEPARATOR}${expiry_timestamp}`;
|
|
84
|
+
const signature = await create_signature(data, password);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
value: `${data}${SEPARATOR}${signature}`,
|
|
88
|
+
max_age: expiry_days * 24 * 60 * 60, // in seconds
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Validates dev lock cookie from request (Edge-compatible)
|
|
94
|
+
* Checks signature validity and expiration
|
|
95
|
+
* @param request - NextRequest object
|
|
96
|
+
* @returns Validation result with valid flag and optional expired flag
|
|
97
|
+
*/
|
|
98
|
+
export async function validate_dev_lock_cookie(
|
|
99
|
+
request: NextRequest
|
|
100
|
+
): Promise<DevLockValidationResult> {
|
|
101
|
+
const password = process.env.HAZO_AUTH_DEV_LOCK_PASSWORD;
|
|
102
|
+
|
|
103
|
+
if (!password) {
|
|
104
|
+
// No password set - cannot validate
|
|
105
|
+
return { valid: false };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const cookie = request.cookies.get(COOKIE_NAME)?.value;
|
|
109
|
+
|
|
110
|
+
if (!cookie) {
|
|
111
|
+
return { valid: false };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const parts = cookie.split(SEPARATOR);
|
|
116
|
+
if (parts.length !== 3) {
|
|
117
|
+
return { valid: false };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const [timestamp_str, expiry_str, signature] = parts;
|
|
121
|
+
const timestamp = parseInt(timestamp_str, 10);
|
|
122
|
+
const expiry_timestamp = parseInt(expiry_str, 10);
|
|
123
|
+
|
|
124
|
+
if (isNaN(timestamp) || isNaN(expiry_timestamp)) {
|
|
125
|
+
return { valid: false };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check expiry
|
|
129
|
+
if (Date.now() > expiry_timestamp) {
|
|
130
|
+
return { valid: false, expired: true };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Verify signature
|
|
134
|
+
const data = `${timestamp}${SEPARATOR}${expiry_timestamp}`;
|
|
135
|
+
const expected_signature = await create_signature(data, password);
|
|
136
|
+
|
|
137
|
+
// Constant-time comparison to prevent timing attacks
|
|
138
|
+
if (!constant_time_compare(signature, expected_signature)) {
|
|
139
|
+
return { valid: false };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { valid: true };
|
|
143
|
+
} catch {
|
|
144
|
+
return { valid: false };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Validates password against environment variable (for unlock endpoint)
|
|
150
|
+
* Uses constant-time comparison to prevent timing attacks
|
|
151
|
+
* @param password - Password to validate
|
|
152
|
+
* @returns true if password matches
|
|
153
|
+
*/
|
|
154
|
+
export function validate_dev_lock_password(password: string): boolean {
|
|
155
|
+
const expected = process.env.HAZO_AUTH_DEV_LOCK_PASSWORD;
|
|
156
|
+
|
|
157
|
+
if (!expected || !password) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return constant_time_compare(password, expected);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Gets the dev lock cookie name
|
|
166
|
+
* Exported for use in API routes when setting the cookie
|
|
167
|
+
* @returns Cookie name string
|
|
168
|
+
*/
|
|
169
|
+
export function get_dev_lock_cookie_name(): string {
|
|
170
|
+
return COOKIE_NAME;
|
|
171
|
+
}
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
// file_description: server-side implementation of hazo_get_auth utility for API routes
|
|
2
2
|
// section: imports
|
|
3
3
|
import { NextRequest } from "next/server";
|
|
4
|
-
import { get_hazo_connect_instance } from "../hazo_connect_instance.server";
|
|
4
|
+
import { get_hazo_connect_instance } from "../hazo_connect_instance.server.js";
|
|
5
5
|
import { createCrudService } from "hazo_connect/server";
|
|
6
|
-
import { create_app_logger } from "../app_logger";
|
|
7
|
-
import { get_filename, get_line_number } from "../utils/api_route_helpers";
|
|
6
|
+
import { create_app_logger } from "../app_logger.js";
|
|
7
|
+
import { get_filename, get_line_number } from "../utils/api_route_helpers.js";
|
|
8
8
|
import type { HazoAuthResult, HazoAuthUser, HazoAuthOptions, ScopeAccessInfo } from "./auth_types";
|
|
9
|
-
import { PermissionError, ScopeAccessError } from "./auth_types";
|
|
10
|
-
import { get_auth_cache } from "./auth_cache";
|
|
11
|
-
import { get_scope_cache, type UserScopeEntry } from "./scope_cache";
|
|
12
|
-
import { get_rate_limiter } from "./auth_rate_limiter";
|
|
13
|
-
import { get_auth_utility_config } from "../auth_utility_config.server";
|
|
14
|
-
import { validate_session_token } from "../services/session_token_service";
|
|
15
|
-
import { is_hrbac_enabled, get_scope_hierarchy_config } from "../scope_hierarchy_config.server";
|
|
16
|
-
import { check_user_scope_access, get_user_scopes, type UserScope } from "../services/user_scope_service";
|
|
17
|
-
import { is_valid_scope_level, type ScopeLevel } from "../services/scope_service";
|
|
9
|
+
import { PermissionError, ScopeAccessError } from "./auth_types.js";
|
|
10
|
+
import { get_auth_cache } from "./auth_cache.js";
|
|
11
|
+
import { get_scope_cache, type UserScopeEntry } from "./scope_cache.js";
|
|
12
|
+
import { get_rate_limiter } from "./auth_rate_limiter.js";
|
|
13
|
+
import { get_auth_utility_config } from "../auth_utility_config.server.js";
|
|
14
|
+
import { validate_session_token } from "../services/session_token_service.js";
|
|
15
|
+
import { is_hrbac_enabled, get_scope_hierarchy_config } from "../scope_hierarchy_config.server.js";
|
|
16
|
+
import { check_user_scope_access, get_user_scopes, type UserScope } from "../services/user_scope_service.js";
|
|
17
|
+
import { is_valid_scope_level, type ScopeLevel } from "../services/scope_service.js";
|
|
18
|
+
import { is_multi_tenancy_enabled, get_multi_tenancy_config } from "../multi_tenancy_config.server.js";
|
|
19
|
+
import { get_org_cache, type OrgCacheEntry } from "./org_cache.js";
|
|
18
20
|
|
|
19
21
|
// section: helpers
|
|
20
22
|
|
|
@@ -70,7 +72,7 @@ async function fetch_user_data_from_db(user_id: string): Promise<{
|
|
|
70
72
|
throw new Error("User is inactive");
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
// Build user object
|
|
75
|
+
// Build user object with base fields
|
|
74
76
|
const user: HazoAuthUser = {
|
|
75
77
|
id: user_db.id as string,
|
|
76
78
|
name: (user_db.name as string | null) || null,
|
|
@@ -80,6 +82,75 @@ async function fetch_user_data_from_db(user_id: string): Promise<{
|
|
|
80
82
|
(user_db.profile_picture_url as string | null) || null,
|
|
81
83
|
};
|
|
82
84
|
|
|
85
|
+
// Fetch org info if multi-tenancy is enabled and user has org_id
|
|
86
|
+
if (is_multi_tenancy_enabled() && user_db.org_id) {
|
|
87
|
+
const mt_config = get_multi_tenancy_config();
|
|
88
|
+
const org_cache = get_org_cache(
|
|
89
|
+
mt_config.org_cache_max_entries,
|
|
90
|
+
mt_config.org_cache_ttl_minutes,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const user_org_id = user_db.org_id as string;
|
|
94
|
+
|
|
95
|
+
// Check org cache first
|
|
96
|
+
let cached_org = org_cache.get(user_org_id);
|
|
97
|
+
|
|
98
|
+
if (!cached_org) {
|
|
99
|
+
// Fetch org info from database
|
|
100
|
+
const org_service = createCrudService(hazoConnect, "hazo_org");
|
|
101
|
+
const orgs = await org_service.findBy({ id: user_org_id });
|
|
102
|
+
|
|
103
|
+
if (Array.isArray(orgs) && orgs.length > 0) {
|
|
104
|
+
const org = orgs[0];
|
|
105
|
+
const org_entry: OrgCacheEntry = {
|
|
106
|
+
org_id: org.id as string,
|
|
107
|
+
org_name: org.name as string,
|
|
108
|
+
parent_org_id: (org.parent_org_id as string | null) || null,
|
|
109
|
+
parent_org_name: null,
|
|
110
|
+
root_org_id: (org.root_org_id as string | null) || null,
|
|
111
|
+
root_org_name: null,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Fetch parent org name if exists
|
|
115
|
+
if (org_entry.parent_org_id) {
|
|
116
|
+
const parent_orgs = await org_service.findBy({ id: org_entry.parent_org_id });
|
|
117
|
+
if (Array.isArray(parent_orgs) && parent_orgs.length > 0) {
|
|
118
|
+
org_entry.parent_org_name = parent_orgs[0].name as string;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Fetch root org name if exists
|
|
123
|
+
if (org_entry.root_org_id) {
|
|
124
|
+
const root_orgs = await org_service.findBy({ id: org_entry.root_org_id });
|
|
125
|
+
if (Array.isArray(root_orgs) && root_orgs.length > 0) {
|
|
126
|
+
org_entry.root_org_name = root_orgs[0].name as string;
|
|
127
|
+
}
|
|
128
|
+
} else if (user_db.root_org_id) {
|
|
129
|
+
// Fallback to user's root_org_id if org doesn't have one
|
|
130
|
+
const root_orgs = await org_service.findBy({ id: user_db.root_org_id });
|
|
131
|
+
if (Array.isArray(root_orgs) && root_orgs.length > 0) {
|
|
132
|
+
org_entry.root_org_id = root_orgs[0].id as string;
|
|
133
|
+
org_entry.root_org_name = root_orgs[0].name as string;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Cache the org info
|
|
138
|
+
org_cache.set(user_org_id, org_entry);
|
|
139
|
+
cached_org = org_entry;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Add org info to user object
|
|
144
|
+
if (cached_org) {
|
|
145
|
+
user.org_id = cached_org.org_id;
|
|
146
|
+
user.org_name = cached_org.org_name;
|
|
147
|
+
user.parent_org_id = cached_org.parent_org_id;
|
|
148
|
+
user.parent_org_name = cached_org.parent_org_name;
|
|
149
|
+
user.root_org_id = cached_org.root_org_id;
|
|
150
|
+
user.root_org_name = cached_org.root_org_name;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
83
154
|
// Fetch user roles
|
|
84
155
|
const user_roles = await user_roles_service.findBy({ user_id });
|
|
85
156
|
const role_ids: number[] = [];
|
|
@@ -3,21 +3,21 @@
|
|
|
3
3
|
export * from "./auth_types";
|
|
4
4
|
|
|
5
5
|
// section: server_exports
|
|
6
|
-
export { hazo_get_auth } from "./hazo_get_auth.server";
|
|
6
|
+
export { hazo_get_auth } from "./hazo_get_auth.server.js";
|
|
7
7
|
export {
|
|
8
8
|
get_authenticated_user,
|
|
9
9
|
require_auth,
|
|
10
10
|
is_authenticated,
|
|
11
|
-
} from "./auth_utils.server";
|
|
11
|
+
} from "./auth_utils.server.js";
|
|
12
12
|
export type { AuthResult, AuthUser } from "./auth_utils.server";
|
|
13
13
|
|
|
14
14
|
// section: client_exports
|
|
15
|
-
export { get_server_auth_user } from "./server_auth";
|
|
15
|
+
export { get_server_auth_user } from "./server_auth.js";
|
|
16
16
|
export type { ServerAuthResult } from "./server_auth";
|
|
17
17
|
|
|
18
18
|
// section: cache_exports
|
|
19
|
-
export { get_auth_cache, reset_auth_cache } from "./auth_cache";
|
|
19
|
+
export { get_auth_cache, reset_auth_cache } from "./auth_cache.js";
|
|
20
20
|
|
|
21
21
|
// section: rate_limiter_exports
|
|
22
|
-
export { get_rate_limiter, reset_rate_limiter } from "./auth_rate_limiter";
|
|
22
|
+
export { get_rate_limiter, reset_rate_limiter } from "./auth_rate_limiter.js";
|
|
23
23
|
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import type { AuthOptions, Session } from "next-auth";
|
|
4
4
|
import type { JWT } from "next-auth/jwt";
|
|
5
5
|
import GoogleProvider from "next-auth/providers/google";
|
|
6
|
-
import { get_oauth_config } from "../oauth_config.server";
|
|
7
|
-
import { handle_google_oauth_login } from "../services/oauth_service";
|
|
8
|
-
import { get_hazo_connect_instance } from "../hazo_connect_instance.server";
|
|
9
|
-
import { create_app_logger } from "../app_logger";
|
|
6
|
+
import { get_oauth_config } from "../oauth_config.server.js";
|
|
7
|
+
import { handle_google_oauth_login } from "../services/oauth_service.js";
|
|
8
|
+
import { get_hazo_connect_instance } from "../hazo_connect_instance.server.js";
|
|
9
|
+
import { create_app_logger } from "../app_logger.js";
|
|
10
10
|
|
|
11
11
|
// section: types
|
|
12
12
|
export type NextAuthCallbackUser = {
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// file_description: LRU cache implementation for organization lookups with TTL and size limits
|
|
2
|
+
// section: types
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cached organization info for hazo_get_auth
|
|
6
|
+
*/
|
|
7
|
+
export type OrgCacheEntry = {
|
|
8
|
+
org_id: string;
|
|
9
|
+
org_name: string;
|
|
10
|
+
parent_org_id: string | null;
|
|
11
|
+
parent_org_name: string | null;
|
|
12
|
+
root_org_id: string | null;
|
|
13
|
+
root_org_name: string | null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Internal cache entry with metadata
|
|
18
|
+
*/
|
|
19
|
+
type CacheItem = {
|
|
20
|
+
entry: OrgCacheEntry;
|
|
21
|
+
timestamp: number; // Unix timestamp in milliseconds
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* LRU cache implementation for organization lookups
|
|
26
|
+
* Uses Map to maintain insertion order for LRU eviction
|
|
27
|
+
*/
|
|
28
|
+
class OrgCache {
|
|
29
|
+
private cache: Map<string, CacheItem>;
|
|
30
|
+
private max_size: number;
|
|
31
|
+
private ttl_ms: number;
|
|
32
|
+
|
|
33
|
+
constructor(max_size: number, ttl_minutes: number) {
|
|
34
|
+
this.cache = new Map();
|
|
35
|
+
this.max_size = max_size;
|
|
36
|
+
this.ttl_ms = ttl_minutes * 60 * 1000;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Gets a cache entry for an organization
|
|
41
|
+
* Returns undefined if not found or expired
|
|
42
|
+
* @param org_id - Organization ID to look up
|
|
43
|
+
* @returns Cache entry or undefined
|
|
44
|
+
*/
|
|
45
|
+
get(org_id: string): OrgCacheEntry | undefined {
|
|
46
|
+
const item = this.cache.get(org_id);
|
|
47
|
+
|
|
48
|
+
if (!item) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
const age = now - item.timestamp;
|
|
54
|
+
|
|
55
|
+
// Check if entry is expired (TTL)
|
|
56
|
+
if (age > this.ttl_ms) {
|
|
57
|
+
this.cache.delete(org_id);
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Move to end (most recently used)
|
|
62
|
+
this.cache.delete(org_id);
|
|
63
|
+
this.cache.set(org_id, item);
|
|
64
|
+
|
|
65
|
+
return item.entry;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Sets a cache entry for an organization
|
|
70
|
+
* Evicts least recently used entries if cache is full
|
|
71
|
+
* @param org_id - Organization ID
|
|
72
|
+
* @param entry - Organization cache entry
|
|
73
|
+
*/
|
|
74
|
+
set(org_id: string, entry: OrgCacheEntry): void {
|
|
75
|
+
// Evict LRU entries if cache is full
|
|
76
|
+
while (this.cache.size >= this.max_size) {
|
|
77
|
+
const first_key = this.cache.keys().next().value;
|
|
78
|
+
if (first_key) {
|
|
79
|
+
this.cache.delete(first_key);
|
|
80
|
+
} else {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const item: CacheItem = {
|
|
86
|
+
entry,
|
|
87
|
+
timestamp: Date.now(),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
this.cache.set(org_id, item);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Invalidates cache for a specific organization
|
|
95
|
+
* @param org_id - Organization ID to invalidate
|
|
96
|
+
*/
|
|
97
|
+
invalidate(org_id: string): void {
|
|
98
|
+
this.cache.delete(org_id);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Invalidates all cache entries
|
|
103
|
+
*/
|
|
104
|
+
invalidate_all(): void {
|
|
105
|
+
this.cache.clear();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Gets cache statistics
|
|
110
|
+
* @returns Object with cache size and max size
|
|
111
|
+
*/
|
|
112
|
+
get_stats(): {
|
|
113
|
+
size: number;
|
|
114
|
+
max_size: number;
|
|
115
|
+
} {
|
|
116
|
+
return {
|
|
117
|
+
size: this.cache.size,
|
|
118
|
+
max_size: this.max_size,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// section: singleton
|
|
124
|
+
// Global org cache instance (initialized with defaults, will be configured on first use)
|
|
125
|
+
let org_cache_instance: OrgCache | null = null;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Gets or creates the global org cache instance
|
|
129
|
+
* @param max_size - Maximum cache size (default: 1000)
|
|
130
|
+
* @param ttl_minutes - TTL in minutes (default: 15)
|
|
131
|
+
* @returns Org cache instance
|
|
132
|
+
*/
|
|
133
|
+
export function get_org_cache(
|
|
134
|
+
max_size: number = 1000,
|
|
135
|
+
ttl_minutes: number = 15,
|
|
136
|
+
): OrgCache {
|
|
137
|
+
if (!org_cache_instance) {
|
|
138
|
+
org_cache_instance = new OrgCache(max_size, ttl_minutes);
|
|
139
|
+
}
|
|
140
|
+
return org_cache_instance;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Resets the global org cache instance (useful for testing)
|
|
145
|
+
*/
|
|
146
|
+
export function reset_org_cache(): void {
|
|
147
|
+
org_cache_instance = null;
|
|
148
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// file_description: server-side auth utilities for server components and pages
|
|
2
2
|
// section: imports
|
|
3
3
|
import { cookies } from "next/headers";
|
|
4
|
-
import { get_hazo_connect_instance } from "../hazo_connect_instance.server";
|
|
4
|
+
import { get_hazo_connect_instance } from "../hazo_connect_instance.server.js";
|
|
5
5
|
import { createCrudService } from "hazo_connect/server";
|
|
6
|
-
import { map_db_source_to_ui } from "../services/profile_picture_source_mapper";
|
|
6
|
+
import { map_db_source_to_ui } from "../services/profile_picture_source_mapper.js";
|
|
7
7
|
|
|
8
8
|
// section: types
|
|
9
9
|
export type ServerAuthUser = {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { HazoConfig } from "hazo_config/dist/lib";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import fs from "fs";
|
|
6
|
-
import { create_app_logger } from "../app_logger";
|
|
6
|
+
import { create_app_logger } from "../app_logger.js";
|
|
7
7
|
|
|
8
8
|
// section: constants
|
|
9
9
|
const DEFAULT_CONFIG_FILE = "hazo_auth_config.ini";
|
|
@@ -165,6 +165,48 @@ export const DEFAULT_OAUTH = {
|
|
|
165
165
|
oauth_divider_text: "or continue with email",
|
|
166
166
|
} as const;
|
|
167
167
|
|
|
168
|
+
// section: multi_tenancy
|
|
169
|
+
export const DEFAULT_MULTI_TENANCY = {
|
|
170
|
+
/** Enable multi-tenancy support (default: false) */
|
|
171
|
+
enable_multi_tenancy: false,
|
|
172
|
+
/** Cache TTL in minutes for org lookups (default: 15) */
|
|
173
|
+
org_cache_ttl_minutes: 15,
|
|
174
|
+
/** Maximum entries in org cache (default: 1000) */
|
|
175
|
+
org_cache_max_entries: 1000,
|
|
176
|
+
/** Default user limit per organization (0 = unlimited) */
|
|
177
|
+
default_user_limit: 0,
|
|
178
|
+
} as const;
|
|
179
|
+
|
|
180
|
+
// section: dev_lock
|
|
181
|
+
export const DEFAULT_DEV_LOCK = {
|
|
182
|
+
/** Enable the development lock screen (also requires HAZO_AUTH_DEV_LOCK_ENABLED env var) */
|
|
183
|
+
enable: false,
|
|
184
|
+
/** Session duration in days */
|
|
185
|
+
session_duration_days: 7,
|
|
186
|
+
/** Background color (default: black) */
|
|
187
|
+
background_color: "#000000",
|
|
188
|
+
/** Logo image path (default: /logo.png in public folder) */
|
|
189
|
+
logo_path: "/logo.png",
|
|
190
|
+
/** Logo width in pixels */
|
|
191
|
+
logo_width: 120,
|
|
192
|
+
/** Logo height in pixels */
|
|
193
|
+
logo_height: 120,
|
|
194
|
+
/** Application name displayed below logo */
|
|
195
|
+
application_name: "",
|
|
196
|
+
/** Limited access text displayed with lock icon */
|
|
197
|
+
limited_access_text: "Limited Access",
|
|
198
|
+
/** Password input placeholder text */
|
|
199
|
+
password_placeholder: "Enter access password",
|
|
200
|
+
/** Submit button text */
|
|
201
|
+
submit_button_text: "Unlock",
|
|
202
|
+
/** Error message for incorrect password */
|
|
203
|
+
error_message: "Incorrect password",
|
|
204
|
+
/** Text color for labels (default: white) */
|
|
205
|
+
text_color: "#ffffff",
|
|
206
|
+
/** Accent color for button (default: blue) */
|
|
207
|
+
accent_color: "#3b82f6",
|
|
208
|
+
} as const;
|
|
209
|
+
|
|
168
210
|
// section: combined_defaults
|
|
169
211
|
/**
|
|
170
212
|
* All default configuration values combined in one object
|
|
@@ -190,6 +232,8 @@ export const HAZO_AUTH_DEFAULTS = {
|
|
|
190
232
|
profilePicMenu: DEFAULT_PROFILE_PIC_MENU,
|
|
191
233
|
apiPaths: DEFAULT_API_PATHS,
|
|
192
234
|
oauth: DEFAULT_OAUTH,
|
|
235
|
+
devLock: DEFAULT_DEV_LOCK,
|
|
236
|
+
multiTenancy: DEFAULT_MULTI_TENANCY,
|
|
193
237
|
} as const;
|
|
194
238
|
|
|
195
239
|
// section: types
|