hazo_auth 5.1.10 → 5.1.12

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.
@@ -85,4 +85,113 @@ export declare class ScopeAccessError extends Error {
85
85
  scope_name?: string;
86
86
  }>);
87
87
  }
88
+ /**
89
+ * Full scope details with branding information
90
+ * Used in cache and tenant auth results for multi-tenancy support
91
+ */
92
+ export type ScopeDetails = {
93
+ id: string;
94
+ name: string;
95
+ slug: string | null;
96
+ level: string;
97
+ parent_id: string | null;
98
+ role_id: string;
99
+ logo_url: string | null;
100
+ primary_color: string | null;
101
+ secondary_color: string | null;
102
+ tagline: string | null;
103
+ };
104
+ /**
105
+ * Tenant/organization information returned in tenant auth results
106
+ * Simplified view of scope for API responses
107
+ */
108
+ export type TenantOrganization = {
109
+ id: string;
110
+ name: string;
111
+ slug: string | null;
112
+ level: string;
113
+ role_id: string;
114
+ is_super_admin: boolean;
115
+ branding?: {
116
+ logo_url: string | null;
117
+ primary_color: string | null;
118
+ secondary_color: string | null;
119
+ tagline: string | null;
120
+ };
121
+ };
122
+ /**
123
+ * Options for hazo_get_tenant_auth function
124
+ * Extends HazoAuthOptions with tenant-specific configuration
125
+ */
126
+ export type TenantAuthOptions = HazoAuthOptions & {
127
+ /**
128
+ * Header name to check for scope ID (default: "X-Hazo-Scope-Id")
129
+ */
130
+ scope_header_name?: string;
131
+ /**
132
+ * Cookie name to check for scope ID (uses cookie prefix if not specified)
133
+ */
134
+ scope_cookie_name?: string;
135
+ };
136
+ /**
137
+ * Result type for hazo_get_tenant_auth function
138
+ * Extends HazoAuthResult with tenant-specific information
139
+ */
140
+ export type TenantAuthResult = {
141
+ authenticated: true;
142
+ user: HazoAuthUser;
143
+ permissions: string[];
144
+ permission_ok: boolean;
145
+ missing_permissions?: string[];
146
+ organization: TenantOrganization | null;
147
+ user_scopes: ScopeDetails[];
148
+ scope_ok?: boolean;
149
+ scope_access_via?: ScopeAccessInfo;
150
+ } | {
151
+ authenticated: false;
152
+ user: null;
153
+ permissions: [];
154
+ permission_ok: false;
155
+ organization: null;
156
+ user_scopes: [];
157
+ scope_ok?: false;
158
+ };
159
+ /**
160
+ * Guaranteed authenticated result with non-null organization
161
+ * Returned by require_tenant_auth when validation passes
162
+ */
163
+ export type RequiredTenantAuthResult = TenantAuthResult & {
164
+ authenticated: true;
165
+ organization: TenantOrganization;
166
+ };
167
+ /**
168
+ * Base error class for all hazo_auth errors
169
+ * Provides error code and HTTP status code for API responses
170
+ */
171
+ export declare class HazoAuthError extends Error {
172
+ readonly code: string;
173
+ readonly status_code: number;
174
+ constructor(message: string, code: string, status_code: number);
175
+ }
176
+ /**
177
+ * Error thrown when authentication is required but user is not authenticated
178
+ */
179
+ export declare class AuthenticationRequiredError extends HazoAuthError {
180
+ constructor(message?: string);
181
+ }
182
+ /**
183
+ * Error thrown when a tenant/scope context is required but not provided
184
+ */
185
+ export declare class TenantRequiredError extends HazoAuthError {
186
+ readonly user_scopes: ScopeDetails[];
187
+ constructor(message?: string, user_scopes?: ScopeDetails[]);
188
+ }
189
+ /**
190
+ * Error thrown when user lacks access to the requested tenant/scope
191
+ */
192
+ export declare class TenantAccessDeniedError extends HazoAuthError {
193
+ readonly scope_id: string;
194
+ readonly user_scopes: ScopeDetails[];
195
+ constructor(scope_id: string, user_scopes?: ScopeDetails[]);
196
+ }
88
197
  //# sourceMappingURL=auth_types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth_types.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/auth_types.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IAEnC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC/C,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB;IACE,aAAa,EAAE,IAAI,CAAC;IACpB,IAAI,EAAE,YAAY,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,eAAe,CAAC;CACpC,GACD;IACE,aAAa,EAAE,KAAK,CAAC;IACrB,IAAI,EAAE,IAAI,CAAC;IACX,WAAW,EAAE,EAAE,CAAC;IAChB,aAAa,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB,CAAC;AAEN;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IAE/B,mBAAmB,EAAE,MAAM,EAAE;IAC7B,gBAAgB,EAAE,MAAM,EAAE;IAC1B,oBAAoB,EAAE,MAAM,EAAE;IAC9B,qBAAqB,CAAC,EAAE,MAAM;IAC9B,uBAAuB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;gBAJ7C,mBAAmB,EAAE,MAAM,EAAE,EAC7B,gBAAgB,EAAE,MAAM,EAAE,EAC1B,oBAAoB,EAAE,MAAM,EAAE,EAC9B,qBAAqB,CAAC,EAAE,MAAM,YAAA,EAC9B,uBAAuB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,YAAA;CAKvD;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IAEhC,QAAQ,EAAE,MAAM;IAChB,WAAW,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;gBAD7D,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAKvE"}
1
+ {"version":3,"file":"auth_types.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/auth_types.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IAEnC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC/C,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB;IACE,aAAa,EAAE,IAAI,CAAC;IACpB,IAAI,EAAE,YAAY,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,eAAe,CAAC;CACpC,GACD;IACE,aAAa,EAAE,KAAK,CAAC;IACrB,IAAI,EAAE,IAAI,CAAC;IACX,WAAW,EAAE,EAAE,CAAC;IAChB,aAAa,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB,CAAC;AAEN;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IAE/B,mBAAmB,EAAE,MAAM,EAAE;IAC7B,gBAAgB,EAAE,MAAM,EAAE;IAC1B,oBAAoB,EAAE,MAAM,EAAE;IAC9B,qBAAqB,CAAC,EAAE,MAAM;IAC9B,uBAAuB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;gBAJ7C,mBAAmB,EAAE,MAAM,EAAE,EAC7B,gBAAgB,EAAE,MAAM,EAAE,EAC1B,oBAAoB,EAAE,MAAM,EAAE,EAC9B,qBAAqB,CAAC,EAAE,MAAM,YAAA,EAC9B,uBAAuB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,YAAA;CAKvD;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IAEhC,QAAQ,EAAE,MAAM;IAChB,WAAW,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;gBAD7D,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAKvE;AAID;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAEhB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE;QACT,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;KACxB,CAAC;CACH,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,eAAe,GAAG;IAChD;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GACxB;IACE,aAAa,EAAE,IAAI,CAAC;IACpB,IAAI,EAAE,YAAY,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,YAAY,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACxC,WAAW,EAAE,YAAY,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,eAAe,CAAC;CACpC,GACD;IACE,aAAa,EAAE,KAAK,CAAC;IACrB,IAAI,EAAE,IAAI,CAAC;IACX,WAAW,EAAE,EAAE,CAAC;IAChB,aAAa,EAAE,KAAK,CAAC;IACrB,YAAY,EAAE,IAAI,CAAC;IACnB,WAAW,EAAE,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB,CAAC;AAEN;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG,gBAAgB,GAAG;IACxD,aAAa,EAAE,IAAI,CAAC;IACpB,YAAY,EAAE,kBAAkB,CAAC;CAClC,CAAC;AAIF;;;GAGG;AACH,qBAAa,aAAc,SAAQ,KAAK;aAGpB,IAAI,EAAE,MAAM;aACZ,WAAW,EAAE,MAAM;gBAFnC,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM;CAKtC;AAED;;GAEG;AACH,qBAAa,2BAA4B,SAAQ,aAAa;gBAChD,OAAO,GAAE,MAAkC;CAIxD;AAED;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,aAAa;aAGlC,WAAW,EAAE,YAAY,EAAE;gBAD3C,OAAO,GAAE,MAAkC,EAC3B,WAAW,GAAE,YAAY,EAAO;CAKnD;AAED;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,aAAa;aAEtC,QAAQ,EAAE,MAAM;aAChB,WAAW,EAAE,YAAY,EAAE;gBAD3B,QAAQ,EAAE,MAAM,EAChB,WAAW,GAAE,YAAY,EAAO;CAKnD"}
@@ -28,3 +28,46 @@ export class ScopeAccessError extends Error {
28
28
  this.name = "ScopeAccessError";
29
29
  }
30
30
  }
31
+ // section: tenant_error_classes
32
+ /**
33
+ * Base error class for all hazo_auth errors
34
+ * Provides error code and HTTP status code for API responses
35
+ */
36
+ export class HazoAuthError extends Error {
37
+ constructor(message, code, status_code) {
38
+ super(message);
39
+ this.code = code;
40
+ this.status_code = status_code;
41
+ this.name = "HazoAuthError";
42
+ }
43
+ }
44
+ /**
45
+ * Error thrown when authentication is required but user is not authenticated
46
+ */
47
+ export class AuthenticationRequiredError extends HazoAuthError {
48
+ constructor(message = "Authentication required") {
49
+ super(message, "AUTHENTICATION_REQUIRED", 401);
50
+ this.name = "AuthenticationRequiredError";
51
+ }
52
+ }
53
+ /**
54
+ * Error thrown when a tenant/scope context is required but not provided
55
+ */
56
+ export class TenantRequiredError extends HazoAuthError {
57
+ constructor(message = "Tenant context required", user_scopes = []) {
58
+ super(message, "TENANT_REQUIRED", 403);
59
+ this.user_scopes = user_scopes;
60
+ this.name = "TenantRequiredError";
61
+ }
62
+ }
63
+ /**
64
+ * Error thrown when user lacks access to the requested tenant/scope
65
+ */
66
+ export class TenantAccessDeniedError extends HazoAuthError {
67
+ constructor(scope_id, user_scopes = []) {
68
+ super(`Access denied to scope: ${scope_id}`, "TENANT_ACCESS_DENIED", 403);
69
+ this.scope_id = scope_id;
70
+ this.user_scopes = user_scopes;
71
+ this.name = "TenantAccessDeniedError";
72
+ }
73
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"hazo_get_auth.server.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/hazo_get_auth.server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK1C,OAAO,KAAK,EACV,cAAc,EAEd,eAAe,EAEhB,MAAM,cAAc,CAAC;AAmTtB;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,cAAc,CAAC,CAmNzB"}
1
+ {"version":3,"file":"hazo_get_auth.server.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/hazo_get_auth.server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK1C,OAAO,KAAK,EACV,cAAc,EAEd,eAAe,EAGhB,MAAM,cAAc,CAAC;AAsWtB;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,cAAc,CAAC,CAmNzB"}
@@ -52,11 +52,6 @@ function get_client_ip(request) {
52
52
  }
53
53
  return "unknown";
54
54
  }
55
- /**
56
- * Fetches user data and permissions from database
57
- * @param user_id - User ID
58
- * @returns Object with user, permissions, and role_ids
59
- */
60
55
  /**
61
56
  * CRUD service options for hazo_user_scopes table
62
57
  * This table uses a composite primary key (user_id, scope_id) and no 'id' column
@@ -65,6 +60,48 @@ const USER_SCOPES_CRUD_OPTIONS = {
65
60
  primaryKeys: ["user_id", "scope_id"],
66
61
  autoId: false,
67
62
  };
63
+ /**
64
+ * Fetches full scope details for user's scope assignments
65
+ * Joins hazo_user_scopes with hazo_scopes to get name, slug, branding
66
+ * @param user_id - User ID
67
+ * @returns Array of scope details with branding information
68
+ */
69
+ async function fetch_user_scope_details(user_id) {
70
+ const hazoConnect = get_hazo_connect_instance();
71
+ const user_scopes_service = createCrudService(hazoConnect, "hazo_user_scopes", USER_SCOPES_CRUD_OPTIONS);
72
+ const scopes_service = createCrudService(hazoConnect, "hazo_scopes");
73
+ const user_scope_assignments = await user_scopes_service.findBy({ user_id });
74
+ if (!Array.isArray(user_scope_assignments) || user_scope_assignments.length === 0) {
75
+ return [];
76
+ }
77
+ const scope_details = [];
78
+ for (const assignment of user_scope_assignments) {
79
+ const scope_id = assignment.scope_id;
80
+ const role_id = assignment.role_id;
81
+ const scopes = await scopes_service.findBy({ id: scope_id });
82
+ if (Array.isArray(scopes) && scopes.length > 0) {
83
+ const scope = scopes[0];
84
+ scope_details.push({
85
+ id: scope_id,
86
+ name: scope.name,
87
+ slug: scope.slug || null,
88
+ level: scope.level,
89
+ parent_id: scope.parent_id,
90
+ role_id,
91
+ logo_url: scope.logo_url || null,
92
+ primary_color: scope.primary_color || null,
93
+ secondary_color: scope.secondary_color || null,
94
+ tagline: scope.tagline || null,
95
+ });
96
+ }
97
+ }
98
+ return scope_details;
99
+ }
100
+ /**
101
+ * Fetches user data and permissions from database
102
+ * @param user_id - User ID
103
+ * @returns Object with user, permissions, role_ids, and scopes
104
+ */
68
105
  async function fetch_user_data_from_db(user_id) {
69
106
  const hazoConnect = get_hazo_connect_instance();
70
107
  const users_service = createCrudService(hazoConnect, "hazo_users");
@@ -138,7 +175,9 @@ async function fetch_user_data_from_db(user_id) {
138
175
  }
139
176
  }
140
177
  const permissions = Array.from(permissions_set);
141
- return { user, permissions, role_ids };
178
+ // v5.2: Fetch full scope details for caching
179
+ const scopes = await fetch_user_scope_details(user_id);
180
+ return { user, permissions, role_ids, scopes };
142
181
  }
143
182
  /**
144
183
  * Checks if user has required permissions
@@ -325,8 +364,8 @@ export async function hazo_get_auth(request, options) {
325
364
  user = user_data.user;
326
365
  permissions = user_data.permissions;
327
366
  role_ids = user_data.role_ids;
328
- // Update cache
329
- cache.set(user_id, user, permissions, role_ids);
367
+ // Update cache (v5.2: includes scope details for tenant auth)
368
+ cache.set(user_id, user, permissions, role_ids, user_data.scopes);
330
369
  }
331
370
  catch (error) {
332
371
  const error_message = error instanceof Error ? error.message : "Unknown error";
@@ -0,0 +1,64 @@
1
+ import { NextRequest } from "next/server";
2
+ import type { TenantAuthOptions, TenantAuthResult, RequiredTenantAuthResult } from "./auth_types";
3
+ /**
4
+ * Extracts scope ID from request headers or cookies
5
+ * Priority: Header > Cookie
6
+ * @param request - NextRequest object
7
+ * @param options - TenantAuthOptions for customization
8
+ * @returns Scope ID if found, undefined otherwise
9
+ */
10
+ export declare function extract_scope_id_from_request(request: NextRequest, options: TenantAuthOptions): string | undefined;
11
+ /**
12
+ * Tenant-aware authentication function
13
+ *
14
+ * Extracts tenant/scope context from request headers or cookies,
15
+ * validates access, and returns enriched result with organization info.
16
+ *
17
+ * Header priority: X-Hazo-Scope-Id > Cookie
18
+ *
19
+ * @param request - NextRequest object
20
+ * @param options - TenantAuthOptions for customization
21
+ * @returns TenantAuthResult with user, permissions, organization, and user_scopes
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const auth = await hazo_get_tenant_auth(request);
26
+ * if (auth.authenticated && auth.organization) {
27
+ * // Access tenant-specific data
28
+ * const data = await getData(auth.organization.id);
29
+ * }
30
+ * ```
31
+ */
32
+ export declare function hazo_get_tenant_auth(request: NextRequest, options?: TenantAuthOptions): Promise<TenantAuthResult>;
33
+ /**
34
+ * Strict tenant authentication helper
35
+ *
36
+ * Wraps hazo_get_tenant_auth and throws appropriate errors:
37
+ * - AuthenticationRequiredError (401) if not authenticated
38
+ * - TenantRequiredError (403) if no tenant context in request
39
+ * - TenantAccessDeniedError (403) if user lacks access to requested tenant
40
+ *
41
+ * @param request - NextRequest object
42
+ * @param options - TenantAuthOptions for customization
43
+ * @returns RequiredTenantAuthResult with guaranteed non-null organization
44
+ * @throws AuthenticationRequiredError, TenantRequiredError, TenantAccessDeniedError
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * try {
49
+ * const auth = await require_tenant_auth(request);
50
+ * // auth.organization is guaranteed non-null here
51
+ * const data = await getData(auth.organization.id);
52
+ * } catch (error) {
53
+ * if (error instanceof HazoAuthError) {
54
+ * return NextResponse.json(
55
+ * { error: error.message, code: error.code },
56
+ * { status: error.status_code }
57
+ * );
58
+ * }
59
+ * throw error;
60
+ * }
61
+ * ```
62
+ */
63
+ export declare function require_tenant_auth(request: NextRequest, options?: TenantAuthOptions): Promise<RequiredTenantAuthResult>;
64
+ //# sourceMappingURL=hazo_get_tenant_auth.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hazo_get_tenant_auth.server.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/hazo_get_tenant_auth.server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO1C,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,wBAAwB,EAGzB,MAAM,cAAc,CAAC;AAqBtB;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,iBAAiB,GACzB,MAAM,GAAG,SAAS,CAYpB;AAiCD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAwF3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,wBAAwB,CAAC,CA0BnC"}
@@ -0,0 +1,201 @@
1
+ import { hazo_get_auth } from "./hazo_get_auth.server.js";
2
+ import { get_auth_cache } from "./auth_cache.js";
3
+ import { get_scope_by_id } from "../services/scope_service.js";
4
+ import { get_hazo_connect_instance } from "../hazo_connect_instance.server.js";
5
+ import { get_cookie_name } from "../cookies_config.server.js";
6
+ import { get_auth_utility_config } from "../auth_utility_config.server.js";
7
+ import { AuthenticationRequiredError, TenantRequiredError, TenantAccessDeniedError, } from "./auth_types.js";
8
+ // section: constants
9
+ /**
10
+ * Default header name for scope ID
11
+ */
12
+ const DEFAULT_SCOPE_HEADER = "X-Hazo-Scope-Id";
13
+ /**
14
+ * Base cookie name for scope ID (will have prefix applied)
15
+ */
16
+ const BASE_SCOPE_COOKIE = "hazo_auth_scope_id";
17
+ // section: helpers
18
+ /**
19
+ * Extracts scope ID from request headers or cookies
20
+ * Priority: Header > Cookie
21
+ * @param request - NextRequest object
22
+ * @param options - TenantAuthOptions for customization
23
+ * @returns Scope ID if found, undefined otherwise
24
+ */
25
+ export function extract_scope_id_from_request(request, options) {
26
+ var _a;
27
+ // Check header first
28
+ const header_name = options.scope_header_name || DEFAULT_SCOPE_HEADER;
29
+ const header_value = request.headers.get(header_name);
30
+ if (header_value) {
31
+ return header_value;
32
+ }
33
+ // Check cookie (with prefix)
34
+ const cookie_name = options.scope_cookie_name || get_cookie_name(BASE_SCOPE_COOKIE);
35
+ const cookie_value = (_a = request.cookies.get(cookie_name)) === null || _a === void 0 ? void 0 : _a.value;
36
+ return cookie_value;
37
+ }
38
+ /**
39
+ * Builds TenantOrganization from scope details and access info
40
+ * @param scope_details - Full scope details from cache
41
+ * @param is_super_admin - Whether user is accessing as super admin
42
+ * @returns TenantOrganization object
43
+ */
44
+ function build_tenant_organization(scope_details, is_super_admin) {
45
+ return {
46
+ id: scope_details.id,
47
+ name: scope_details.name,
48
+ slug: scope_details.slug,
49
+ level: scope_details.level,
50
+ role_id: scope_details.role_id,
51
+ is_super_admin,
52
+ branding: scope_details.logo_url || scope_details.primary_color
53
+ ? {
54
+ logo_url: scope_details.logo_url,
55
+ primary_color: scope_details.primary_color,
56
+ secondary_color: scope_details.secondary_color,
57
+ tagline: scope_details.tagline,
58
+ }
59
+ : undefined,
60
+ };
61
+ }
62
+ // section: main_functions
63
+ /**
64
+ * Tenant-aware authentication function
65
+ *
66
+ * Extracts tenant/scope context from request headers or cookies,
67
+ * validates access, and returns enriched result with organization info.
68
+ *
69
+ * Header priority: X-Hazo-Scope-Id > Cookie
70
+ *
71
+ * @param request - NextRequest object
72
+ * @param options - TenantAuthOptions for customization
73
+ * @returns TenantAuthResult with user, permissions, organization, and user_scopes
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * const auth = await hazo_get_tenant_auth(request);
78
+ * if (auth.authenticated && auth.organization) {
79
+ * // Access tenant-specific data
80
+ * const data = await getData(auth.organization.id);
81
+ * }
82
+ * ```
83
+ */
84
+ export async function hazo_get_tenant_auth(request, options = {}) {
85
+ // Extract scope_id from request
86
+ const scope_id = extract_scope_id_from_request(request, options);
87
+ // Build hazo_get_auth options, passing through scope_id if present
88
+ const auth_options = Object.assign(Object.assign({}, options), { scope_id: scope_id || options.scope_id });
89
+ // Call base hazo_get_auth
90
+ const auth_result = await hazo_get_auth(request, auth_options);
91
+ // Handle unauthenticated case
92
+ if (!auth_result.authenticated) {
93
+ return {
94
+ authenticated: false,
95
+ user: null,
96
+ permissions: [],
97
+ permission_ok: false,
98
+ organization: null,
99
+ user_scopes: [],
100
+ scope_ok: false,
101
+ };
102
+ }
103
+ // Get user's scopes from cache (already populated by hazo_get_auth)
104
+ const config = get_auth_utility_config();
105
+ const cache = get_auth_cache(config.cache_max_users, config.cache_ttl_minutes, config.cache_max_age_minutes);
106
+ const cached = cache.get(auth_result.user.id);
107
+ // User scopes from cache or empty array
108
+ const user_scopes = (cached === null || cached === void 0 ? void 0 : cached.scopes) || [];
109
+ // Build organization info if scope access was successful
110
+ let organization = null;
111
+ if (scope_id && auth_result.scope_ok && auth_result.scope_access_via) {
112
+ // Find the scope in user's scopes that matches the access_via scope
113
+ const access_scope = user_scopes.find((s) => { var _a; return s.id === ((_a = auth_result.scope_access_via) === null || _a === void 0 ? void 0 : _a.scope_id); });
114
+ if (access_scope) {
115
+ organization = build_tenant_organization(access_scope, auth_result.scope_access_via.is_super_admin || false);
116
+ }
117
+ else if (auth_result.scope_access_via.is_super_admin) {
118
+ // Super admin accessing scope they're not assigned to - fetch scope details
119
+ const hazoConnect = get_hazo_connect_instance();
120
+ const scope_result = await get_scope_by_id(hazoConnect, scope_id);
121
+ if (scope_result.success && scope_result.scope) {
122
+ organization = {
123
+ id: scope_result.scope.id,
124
+ name: scope_result.scope.name,
125
+ slug: null, // Could fetch from scope if slug column exists
126
+ level: scope_result.scope.level,
127
+ role_id: "", // Super admin doesn't have a role in the scope
128
+ is_super_admin: true,
129
+ branding: scope_result.scope.logo_url
130
+ ? {
131
+ logo_url: scope_result.scope.logo_url,
132
+ primary_color: scope_result.scope.primary_color,
133
+ secondary_color: scope_result.scope.secondary_color,
134
+ tagline: scope_result.scope.tagline,
135
+ }
136
+ : undefined,
137
+ };
138
+ }
139
+ }
140
+ }
141
+ return {
142
+ authenticated: true,
143
+ user: auth_result.user,
144
+ permissions: auth_result.permissions,
145
+ permission_ok: auth_result.permission_ok,
146
+ missing_permissions: auth_result.missing_permissions,
147
+ organization,
148
+ user_scopes,
149
+ scope_ok: auth_result.scope_ok,
150
+ scope_access_via: auth_result.scope_access_via,
151
+ };
152
+ }
153
+ /**
154
+ * Strict tenant authentication helper
155
+ *
156
+ * Wraps hazo_get_tenant_auth and throws appropriate errors:
157
+ * - AuthenticationRequiredError (401) if not authenticated
158
+ * - TenantRequiredError (403) if no tenant context in request
159
+ * - TenantAccessDeniedError (403) if user lacks access to requested tenant
160
+ *
161
+ * @param request - NextRequest object
162
+ * @param options - TenantAuthOptions for customization
163
+ * @returns RequiredTenantAuthResult with guaranteed non-null organization
164
+ * @throws AuthenticationRequiredError, TenantRequiredError, TenantAccessDeniedError
165
+ *
166
+ * @example
167
+ * ```typescript
168
+ * try {
169
+ * const auth = await require_tenant_auth(request);
170
+ * // auth.organization is guaranteed non-null here
171
+ * const data = await getData(auth.organization.id);
172
+ * } catch (error) {
173
+ * if (error instanceof HazoAuthError) {
174
+ * return NextResponse.json(
175
+ * { error: error.message, code: error.code },
176
+ * { status: error.status_code }
177
+ * );
178
+ * }
179
+ * throw error;
180
+ * }
181
+ * ```
182
+ */
183
+ export async function require_tenant_auth(request, options = {}) {
184
+ const result = await hazo_get_tenant_auth(request, options);
185
+ // Check authentication
186
+ if (!result.authenticated) {
187
+ throw new AuthenticationRequiredError();
188
+ }
189
+ // Extract scope_id from request for error messages
190
+ const scope_id = extract_scope_id_from_request(request, options);
191
+ // Check if scope was requested but access denied
192
+ if (scope_id && !result.scope_ok) {
193
+ throw new TenantAccessDeniedError(scope_id, result.user_scopes);
194
+ }
195
+ // Check if organization context is required but missing
196
+ if (!result.organization) {
197
+ throw new TenantRequiredError("No organization context provided. Include X-Hazo-Scope-Id header or scope cookie.", result.user_scopes);
198
+ }
199
+ // Type assertion: at this point we know organization is non-null
200
+ return result;
201
+ }
@@ -2,6 +2,9 @@ export * from "./auth_types.js";
2
2
  export { hazo_get_auth } from "./hazo_get_auth.server.js";
3
3
  export { get_authenticated_user, require_auth, is_authenticated, } from "./auth_utils.server.js";
4
4
  export type { AuthResult, AuthUser } from "./auth_utils.server";
5
+ export { hazo_get_tenant_auth, require_tenant_auth, extract_scope_id_from_request, } from "./hazo_get_tenant_auth.server.js";
6
+ export type { ScopeDetails, TenantOrganization, TenantAuthOptions, TenantAuthResult, RequiredTenantAuthResult, } from "./auth_types";
7
+ export { HazoAuthError, AuthenticationRequiredError, TenantRequiredError, TenantAccessDeniedError, } from "./auth_types.js";
5
8
  export { get_server_auth_user } from "./server_auth.js";
6
9
  export type { ServerAuthResult } from "./server_auth";
7
10
  export { get_auth_cache, reset_auth_cache } from "./auth_cache.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/index.ts"],"names":[],"mappings":"AAEA,cAAc,cAAc,CAAC;AAG7B,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGhE,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/index.ts"],"names":[],"mappings":"AAEA,cAAc,cAAc,CAAC;AAG7B,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGhE,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,6BAA6B,GAC9B,MAAM,+BAA+B,CAAC;AACvC,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,aAAa,EACb,2BAA2B,EAC3B,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -4,6 +4,9 @@ export * from "./auth_types.js";
4
4
  // section: server_exports
5
5
  export { hazo_get_auth } from "./hazo_get_auth.server.js";
6
6
  export { get_authenticated_user, require_auth, is_authenticated, } from "./auth_utils.server.js";
7
+ // section: tenant_auth_exports
8
+ export { hazo_get_tenant_auth, require_tenant_auth, extract_scope_id_from_request, } from "./hazo_get_tenant_auth.server.js";
9
+ export { HazoAuthError, AuthenticationRequiredError, TenantRequiredError, TenantAccessDeniedError, } from "./auth_types.js";
7
10
  // section: client_exports
8
11
  export { get_server_auth_user } from "./server_auth.js";
9
12
  // section: cache_exports
@@ -9,6 +9,7 @@ export declare const BASE_COOKIE_NAMES: {
9
9
  readonly USER_EMAIL: "hazo_auth_user_email";
10
10
  readonly SESSION: "hazo_auth_session";
11
11
  readonly DEV_LOCK: "hazo_auth_dev_lock";
12
+ readonly SCOPE_ID: "hazo_auth_scope_id";
12
13
  };
13
14
  /**
14
15
  * Reads cookie configuration from hazo_auth_config.ini file
@@ -1 +1 @@
1
- {"version":3,"file":"cookies_config.server.d.ts","sourceRoot":"","sources":["../../src/lib/cookies_config.server.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,aAAa,GAAG;IAC1B,6FAA6F;IAC7F,aAAa,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAYF,eAAO,MAAM,iBAAiB;;;;;CAKpB,CAAC;AAGX;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,aAAa,CAWlD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAQzG;AAKD;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,aAAa,CAKzD;AAED;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD"}
1
+ {"version":3,"file":"cookies_config.server.d.ts","sourceRoot":"","sources":["../../src/lib/cookies_config.server.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,aAAa,GAAG;IAC1B,6FAA6F;IAC7F,aAAa,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAYF,eAAO,MAAM,iBAAiB;;;;;;CAMpB,CAAC;AAGX;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,aAAa,CAWlD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAQzG;AAKD;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,aAAa,CAKzD;AAED;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD"}
@@ -13,6 +13,7 @@ export const BASE_COOKIE_NAMES = {
13
13
  USER_EMAIL: "hazo_auth_user_email",
14
14
  SESSION: "hazo_auth_session",
15
15
  DEV_LOCK: "hazo_auth_dev_lock",
16
+ SCOPE_ID: "hazo_auth_scope_id", // v5.2: Tenant context cookie for multi-tenancy
16
17
  };
17
18
  // section: main_function
18
19
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hazo_auth",
3
- "version": "5.1.10",
3
+ "version": "5.1.12",
4
4
  "description": "Zero-config authentication UI components for Next.js with RBAC, OAuth, scope-based multi-tenancy, and invitations",
5
5
  "keywords": [
6
6
  "authentication",