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.
Files changed (119) hide show
  1. package/cli-src/lib/already_logged_in_config.server.ts +1 -1
  2. package/cli-src/lib/app_logger.ts +8 -18
  3. package/cli-src/lib/auth/auth_types.ts +7 -0
  4. package/cli-src/lib/auth/auth_utils.server.ts +2 -2
  5. package/cli-src/lib/auth/dev_lock_validator.edge.ts +171 -0
  6. package/cli-src/lib/auth/hazo_get_auth.server.ts +84 -13
  7. package/cli-src/lib/auth/index.ts +5 -5
  8. package/cli-src/lib/auth/nextauth_config.ts +4 -4
  9. package/cli-src/lib/auth/org_cache.ts +148 -0
  10. package/cli-src/lib/auth/server_auth.ts +2 -2
  11. package/cli-src/lib/auth/session_token_validator.edge.ts +4 -0
  12. package/cli-src/lib/auth_utility_config.server.ts +1 -1
  13. package/cli-src/lib/config/config_loader.server.ts +1 -1
  14. package/cli-src/lib/config/default_config.ts +44 -0
  15. package/cli-src/lib/dev_lock_config.server.ts +148 -0
  16. package/cli-src/lib/email_verification_config.server.ts +3 -3
  17. package/cli-src/lib/file_types_config.server.ts +1 -1
  18. package/cli-src/lib/forgot_password_config.server.ts +3 -3
  19. package/cli-src/lib/hazo_connect_instance.server.ts +2 -2
  20. package/cli-src/lib/hazo_connect_setup.server.ts +2 -2
  21. package/cli-src/lib/index.ts +24 -24
  22. package/cli-src/lib/login_config.server.ts +4 -4
  23. package/cli-src/lib/messages_config.server.ts +1 -1
  24. package/cli-src/lib/multi_tenancy_config.server.ts +94 -0
  25. package/cli-src/lib/my_settings_config.server.ts +7 -7
  26. package/cli-src/lib/oauth_config.server.ts +2 -2
  27. package/cli-src/lib/password_requirements_config.server.ts +2 -2
  28. package/cli-src/lib/profile_pic_menu_config.server.ts +1 -1
  29. package/cli-src/lib/profile_picture_config.server.ts +2 -2
  30. package/cli-src/lib/register_config.server.ts +5 -5
  31. package/cli-src/lib/reset_password_config.server.ts +4 -4
  32. package/cli-src/lib/scope_hierarchy_config.server.ts +2 -2
  33. package/cli-src/lib/services/email_service.ts +2 -2
  34. package/cli-src/lib/services/email_verification_service.ts +3 -3
  35. package/cli-src/lib/services/login_service.ts +3 -3
  36. package/cli-src/lib/services/oauth_service.ts +4 -4
  37. package/cli-src/lib/services/org_service.ts +965 -0
  38. package/cli-src/lib/services/password_change_service.ts +3 -3
  39. package/cli-src/lib/services/password_reset_service.ts +3 -3
  40. package/cli-src/lib/services/profile_picture_remove_service.ts +3 -3
  41. package/cli-src/lib/services/profile_picture_service.ts +5 -5
  42. package/cli-src/lib/services/registration_service.ts +8 -8
  43. package/cli-src/lib/services/scope_labels_service.ts +3 -3
  44. package/cli-src/lib/services/scope_service.ts +2 -2
  45. package/cli-src/lib/services/session_token_service.ts +6 -2
  46. package/cli-src/lib/services/token_service.ts +2 -2
  47. package/cli-src/lib/services/user_profiles_service.ts +4 -4
  48. package/cli-src/lib/services/user_scope_service.ts +3 -3
  49. package/cli-src/lib/services/user_update_service.ts +4 -4
  50. package/cli-src/lib/ui_shell_config.server.ts +1 -1
  51. package/cli-src/lib/ui_sizes_config.server.ts +1 -1
  52. package/cli-src/lib/user_fields_config.server.ts +1 -1
  53. package/cli-src/lib/user_management_config.server.ts +1 -1
  54. package/cli-src/lib/user_profiles_config.server.ts +1 -1
  55. package/cli-src/lib/utils/error_sanitizer.ts +1 -1
  56. package/cli-src/server/types/app_types.ts +72 -0
  57. package/cli-src/server/types/express.d.ts +16 -0
  58. package/dist/components/layouts/dev_lock/index.d.ts +29 -0
  59. package/dist/components/layouts/dev_lock/index.d.ts.map +1 -0
  60. package/dist/components/layouts/dev_lock/index.js +60 -0
  61. package/dist/components/layouts/index.d.ts +2 -0
  62. package/dist/components/layouts/index.d.ts.map +1 -1
  63. package/dist/components/layouts/index.js +1 -0
  64. package/dist/components/layouts/login/hooks/use_login_form.js +2 -2
  65. package/dist/components/layouts/org_management/index.d.ts +26 -0
  66. package/dist/components/layouts/org_management/index.d.ts.map +1 -0
  67. package/dist/components/layouts/org_management/index.js +75 -0
  68. package/dist/components/layouts/user_management/components/org_hierarchy_tab.d.ts +13 -0
  69. package/dist/components/layouts/user_management/components/org_hierarchy_tab.d.ts.map +1 -0
  70. package/dist/components/layouts/user_management/components/org_hierarchy_tab.js +276 -0
  71. package/dist/components/layouts/user_management/index.d.ts +3 -1
  72. package/dist/components/layouts/user_management/index.d.ts.map +1 -1
  73. package/dist/components/layouts/user_management/index.js +10 -4
  74. package/dist/components/ui/button.d.ts +1 -1
  75. package/dist/lib/app_logger.d.ts +3 -9
  76. package/dist/lib/app_logger.d.ts.map +1 -1
  77. package/dist/lib/app_logger.js +7 -10
  78. package/dist/lib/auth/auth_types.d.ts +6 -0
  79. package/dist/lib/auth/auth_types.d.ts.map +1 -1
  80. package/dist/lib/auth/dev_lock_validator.edge.d.ts +38 -0
  81. package/dist/lib/auth/dev_lock_validator.edge.d.ts.map +1 -0
  82. package/dist/lib/auth/dev_lock_validator.edge.js +122 -0
  83. package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
  84. package/dist/lib/auth/hazo_get_auth.server.js +61 -1
  85. package/dist/lib/auth/org_cache.d.ts +65 -0
  86. package/dist/lib/auth/org_cache.d.ts.map +1 -0
  87. package/dist/lib/auth/org_cache.js +103 -0
  88. package/dist/lib/config/default_config.d.ts +76 -0
  89. package/dist/lib/config/default_config.d.ts.map +1 -1
  90. package/dist/lib/config/default_config.js +42 -0
  91. package/dist/lib/dev_lock_config.server.d.ts +41 -0
  92. package/dist/lib/dev_lock_config.server.d.ts.map +1 -0
  93. package/dist/lib/dev_lock_config.server.js +50 -0
  94. package/dist/lib/multi_tenancy_config.server.d.ts +30 -0
  95. package/dist/lib/multi_tenancy_config.server.d.ts.map +1 -0
  96. package/dist/lib/multi_tenancy_config.server.js +41 -0
  97. package/dist/lib/services/org_service.d.ts +191 -0
  98. package/dist/lib/services/org_service.d.ts.map +1 -0
  99. package/dist/lib/services/org_service.js +746 -0
  100. package/dist/page_components/dev_lock.d.ts +11 -0
  101. package/dist/page_components/dev_lock.d.ts.map +1 -0
  102. package/dist/page_components/dev_lock.js +17 -0
  103. package/dist/page_components/index.d.ts +1 -0
  104. package/dist/page_components/index.d.ts.map +1 -1
  105. package/dist/page_components/index.js +1 -0
  106. package/dist/page_components/login.d.ts.map +1 -1
  107. package/dist/page_components/login.js +3 -7
  108. package/dist/page_components/org_management.d.ts +27 -0
  109. package/dist/page_components/org_management.d.ts.map +1 -0
  110. package/dist/page_components/org_management.js +18 -0
  111. package/dist/server/config/config_loader.js +2 -2
  112. package/dist/server/index.d.ts.map +1 -1
  113. package/dist/server/index.js +2 -3
  114. package/dist/server/types/app_types.d.ts +3 -7
  115. package/dist/server/types/app_types.d.ts.map +1 -1
  116. package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
  117. package/dist/server_pages/login_client_wrapper.js +1 -3
  118. package/hazo_auth_config.example.ini +30 -0
  119. 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 { create_logger_service } from "../server/logging/logger_service";
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
- * Creates a logger service instance for use in UI components
11
- * This uses the main app logging service and can be extended with an external logger
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 = {
@@ -89,3 +89,7 @@ export async function validate_session_cookie(
89
89
 
90
90
 
91
91
 
92
+
93
+
94
+
95
+
@@ -5,7 +5,7 @@ import {
5
5
  get_config_number,
6
6
  get_config_boolean,
7
7
  get_config_array,
8
- } from "./config/config_loader.server";
8
+ } from "./config/config_loader.server.js";
9
9
 
10
10
  // section: types
11
11
 
@@ -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