hazo_auth 0.3.0 → 1.0.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 (88) hide show
  1. package/README.md +628 -1
  2. package/hazo_auth_config.example.ini +39 -0
  3. package/instrumentation.ts +1 -1
  4. package/next.config.mjs +1 -1
  5. package/package.json +3 -1
  6. package/src/app/api/{auth → hazo_auth/auth}/upload_profile_picture/route.ts +2 -2
  7. package/src/app/api/{auth → hazo_auth}/change_password/route.ts +23 -0
  8. package/src/app/api/hazo_auth/get_auth/route.ts +89 -0
  9. package/src/app/api/hazo_auth/invalidate_cache/route.ts +139 -0
  10. package/src/app/api/{auth → hazo_auth}/logout/route.ts +27 -0
  11. package/src/app/api/hazo_auth/upload_profile_picture/route.ts +268 -0
  12. package/src/app/api/hazo_auth/user_management/permissions/route.ts +367 -0
  13. package/src/app/api/hazo_auth/user_management/roles/route.ts +442 -0
  14. package/src/app/api/hazo_auth/user_management/users/roles/route.ts +367 -0
  15. package/src/app/api/hazo_auth/user_management/users/route.ts +239 -0
  16. package/src/app/api/{auth → hazo_auth}/validate_reset_token/route.ts +3 -0
  17. package/src/app/api/{auth → hazo_auth}/verify_email/route.ts +3 -0
  18. package/src/app/globals.css +1 -1
  19. package/src/app/hazo_auth/user_management/page.tsx +14 -0
  20. package/src/app/hazo_auth/user_management/user_management_page_client.tsx +16 -0
  21. package/src/app/hazo_connect/api/sqlite/data/route.ts +7 -1
  22. package/src/app/hazo_connect/api/sqlite/schema/route.ts +14 -4
  23. package/src/app/hazo_connect/api/sqlite/tables/route.ts +14 -4
  24. package/src/app/hazo_connect/sqlite_admin/sqlite-admin-client.tsx +40 -3
  25. package/src/app/layout.tsx +1 -1
  26. package/src/app/page.tsx +4 -4
  27. package/src/components/layouts/email_verification/hooks/use_email_verification.ts +4 -4
  28. package/src/components/layouts/email_verification/index.tsx +1 -1
  29. package/src/components/layouts/forgot_password/hooks/use_forgot_password_form.ts +1 -1
  30. package/src/components/layouts/login/hooks/use_login_form.ts +2 -2
  31. package/src/components/layouts/my_settings/components/profile_picture_dialog.tsx +1 -1
  32. package/src/components/layouts/my_settings/components/profile_picture_library_tab.tsx +2 -2
  33. package/src/components/layouts/my_settings/hooks/use_my_settings.ts +5 -5
  34. package/src/components/layouts/my_settings/index.tsx +1 -1
  35. package/src/components/layouts/register/hooks/use_register_form.ts +1 -1
  36. package/src/components/layouts/reset_password/hooks/use_reset_password_form.ts +3 -3
  37. package/src/components/layouts/reset_password/index.tsx +2 -2
  38. package/src/components/layouts/shared/components/logout_button.tsx +1 -1
  39. package/src/components/layouts/shared/components/profile_pic_menu.tsx +4 -4
  40. package/src/components/layouts/shared/components/sidebar_layout_wrapper.tsx +19 -7
  41. package/src/components/layouts/shared/components/unauthorized_guard.tsx +1 -1
  42. package/src/components/layouts/shared/hooks/use_auth_status.ts +1 -1
  43. package/src/components/layouts/shared/hooks/use_hazo_auth.ts +158 -0
  44. package/src/components/layouts/user_management/components/roles_matrix.tsx +607 -0
  45. package/src/components/layouts/user_management/index.tsx +1295 -0
  46. package/src/components/ui/alert-dialog.tsx +141 -0
  47. package/src/components/ui/checkbox.tsx +30 -0
  48. package/src/components/ui/table.tsx +120 -0
  49. package/src/lib/auth/auth_cache.ts +220 -0
  50. package/src/lib/auth/auth_rate_limiter.ts +121 -0
  51. package/src/lib/auth/auth_types.ts +65 -0
  52. package/src/lib/auth/hazo_get_auth.server.ts +333 -0
  53. package/src/lib/auth_utility_config.server.ts +136 -0
  54. package/src/lib/hazo_connect_setup.server.ts +2 -3
  55. package/src/lib/my_settings_config.server.ts +1 -1
  56. package/src/lib/profile_pic_menu_config.server.ts +4 -4
  57. package/src/lib/reset_password_config.server.ts +5 -5
  58. package/src/lib/services/email_service.ts +2 -2
  59. package/src/lib/services/profile_picture_remove_service.ts +1 -1
  60. package/src/lib/services/token_service.ts +2 -2
  61. package/src/lib/user_management_config.server.ts +40 -0
  62. package/src/lib/utils.ts +1 -1
  63. package/src/middleware.ts +15 -13
  64. package/src/server/types/express.d.ts +1 -0
  65. package/src/stories/project_overview.stories.tsx +1 -1
  66. package/tailwind.config.ts +1 -1
  67. /package/src/app/api/{auth → hazo_auth}/forgot_password/route.ts +0 -0
  68. /package/src/app/api/{auth → hazo_auth}/library_photos/route.ts +0 -0
  69. /package/src/app/api/{auth → hazo_auth}/login/route.ts +0 -0
  70. /package/src/app/api/{auth → hazo_auth}/me/route.ts +0 -0
  71. /package/src/app/api/{auth → hazo_auth}/profile_picture/[filename]/route.ts +0 -0
  72. /package/src/app/api/{auth → hazo_auth}/register/route.ts +0 -0
  73. /package/src/app/api/{auth → hazo_auth}/remove_profile_picture/route.ts +0 -0
  74. /package/src/app/api/{auth → hazo_auth}/resend_verification/route.ts +0 -0
  75. /package/src/app/api/{auth → hazo_auth}/reset_password/route.ts +0 -0
  76. /package/src/app/api/{auth → hazo_auth}/update_user/route.ts +0 -0
  77. /package/src/app/{forgot_password → hazo_auth/forgot_password}/forgot_password_page_client.tsx +0 -0
  78. /package/src/app/{forgot_password → hazo_auth/forgot_password}/page.tsx +0 -0
  79. /package/src/app/{login → hazo_auth/login}/login_page_client.tsx +0 -0
  80. /package/src/app/{login → hazo_auth/login}/page.tsx +0 -0
  81. /package/src/app/{my_settings → hazo_auth/my_settings}/my_settings_page_client.tsx +0 -0
  82. /package/src/app/{my_settings → hazo_auth/my_settings}/page.tsx +0 -0
  83. /package/src/app/{register → hazo_auth/register}/page.tsx +0 -0
  84. /package/src/app/{register → hazo_auth/register}/register_page_client.tsx +0 -0
  85. /package/src/app/{reset_password → hazo_auth/reset_password}/page.tsx +0 -0
  86. /package/src/app/{reset_password → hazo_auth/reset_password}/reset_password_page_client.tsx +0 -0
  87. /package/src/app/{verify_email → hazo_auth/verify_email}/page.tsx +0 -0
  88. /package/src/app/{verify_email → hazo_auth/verify_email}/verify_email_page_client.tsx +0 -0
@@ -19,11 +19,21 @@ export async function GET(request: NextRequest) {
19
19
  // Get singleton hazo_connect instance (initializes admin service if needed)
20
20
  get_hazo_connect_instance();
21
21
 
22
- const service = getSqliteAdminService()
23
- const schema = await service.getTableSchema(table)
24
- return NextResponse.json({ data: schema })
22
+ let service;
23
+ try {
24
+ service = getSqliteAdminService();
25
+ } catch (serviceError) {
26
+ const errorMessage = serviceError instanceof Error ? serviceError.message : "Unknown error";
27
+ return NextResponse.json(
28
+ { error: `SQLite Admin Service not available: ${errorMessage}. Make sure enable_admin_ui is set to true in hazo_auth_config.ini.` },
29
+ { status: 500 }
30
+ );
31
+ }
32
+
33
+ const schema = await service.getTableSchema(table);
34
+ return NextResponse.json({ data: schema });
25
35
  } catch (error) {
26
- return toErrorResponse(error, `Failed to fetch schema for table '${table}'`)
36
+ return toErrorResponse(error, `Failed to fetch schema for table '${table}'`);
27
37
  }
28
38
  }
29
39
 
@@ -10,11 +10,21 @@ export async function GET() {
10
10
  // Get singleton hazo_connect instance (initializes admin service if needed)
11
11
  get_hazo_connect_instance();
12
12
 
13
- const service = getSqliteAdminService()
14
- const tables = await service.listTables()
15
- return NextResponse.json({ data: tables })
13
+ let service;
14
+ try {
15
+ service = getSqliteAdminService();
16
+ } catch (serviceError) {
17
+ const errorMessage = serviceError instanceof Error ? serviceError.message : "Unknown error";
18
+ return NextResponse.json(
19
+ { error: `SQLite Admin Service not available: ${errorMessage}. Make sure enable_admin_ui is set to true in hazo_auth_config.ini.` },
20
+ { status: 500 }
21
+ );
22
+ }
23
+
24
+ const tables = await service.listTables();
25
+ return NextResponse.json({ data: tables });
16
26
  } catch (error) {
17
- return toErrorResponse(error, "Failed to list SQLite tables")
27
+ return toErrorResponse(error, "Failed to list SQLite tables");
18
28
  }
19
29
  }
20
30
 
@@ -91,7 +91,19 @@ export default function SqliteAdminClient({
91
91
  try {
92
92
  const schemaResponse = await fetch(`/hazo_connect/api/sqlite/schema?table=${encodeURIComponent(tableName)}`)
93
93
  if (!schemaResponse.ok) {
94
- throw new Error(await schemaResponse.text())
94
+ const contentType = schemaResponse.headers.get("content-type");
95
+ if (contentType && contentType.includes("application/json")) {
96
+ const errorData = await schemaResponse.json();
97
+ throw new Error(errorData.error || `Failed to fetch schema: ${schemaResponse.statusText}`);
98
+ } else {
99
+ const errorText = await schemaResponse.text();
100
+ throw new Error(`Failed to fetch schema: ${errorText.substring(0, 200)}`);
101
+ }
102
+ }
103
+ const contentType = schemaResponse.headers.get("content-type");
104
+ if (!contentType || !contentType.includes("application/json")) {
105
+ const text = await schemaResponse.text();
106
+ throw new Error(`Expected JSON but received ${contentType || "unknown content type"}. Response: ${text.substring(0, 200)}`);
95
107
  }
96
108
  const schemaJson = await schemaResponse.json()
97
109
  setSchema(schemaJson.data as TableSchema)
@@ -170,7 +182,20 @@ export default function SqliteAdminClient({
170
182
 
171
183
  const response = await fetch(`/hazo_connect/api/sqlite/data?${params.toString()}`)
172
184
  if (!response.ok) {
173
- throw new Error(await response.text())
185
+ const contentType = response.headers.get("content-type");
186
+ if (contentType && contentType.includes("application/json")) {
187
+ const errorData = await response.json();
188
+ throw new Error(errorData.error || `Failed to load data: ${response.statusText}`);
189
+ } else {
190
+ const errorText = await response.text();
191
+ throw new Error(`Failed to load data: ${errorText.substring(0, 200)}`);
192
+ }
193
+ }
194
+
195
+ const contentType = response.headers.get("content-type");
196
+ if (!contentType || !contentType.includes("application/json")) {
197
+ const text = await response.text();
198
+ throw new Error(`Expected JSON but received ${contentType || "unknown content type"}. Response: ${text.substring(0, 200)}`);
174
199
  }
175
200
 
176
201
  const json = (await response.json()) as DataResponse
@@ -197,7 +222,19 @@ export default function SqliteAdminClient({
197
222
  try {
198
223
  const response = await fetch("/hazo_connect/api/sqlite/tables")
199
224
  if (!response.ok) {
200
- throw new Error(await response.text())
225
+ const contentType = response.headers.get("content-type");
226
+ if (contentType && contentType.includes("application/json")) {
227
+ const errorData = await response.json();
228
+ throw new Error(errorData.error || `Failed to refresh tables: ${response.statusText}`);
229
+ } else {
230
+ const errorText = await response.text();
231
+ throw new Error(`Failed to refresh tables: ${errorText.substring(0, 200)}`);
232
+ }
233
+ }
234
+ const contentType = response.headers.get("content-type");
235
+ if (!contentType || !contentType.includes("application/json")) {
236
+ const text = await response.text();
237
+ throw new Error(`Expected JSON but received ${contentType || "unknown content type"}. Response: ${text.substring(0, 200)}`);
201
238
  }
202
239
  const json = await response.json()
203
240
  setTables(json.data ?? [])
@@ -1,4 +1,4 @@
1
- // file_description: define the root layout wrapper for the ui_component project
1
+ // file_description: define the root layout wrapper for the hazo_auth project
2
2
  import type { Metadata } from "next";
3
3
  import local_font from "next/font/local";
4
4
  import { Toaster } from "@/components/ui/sonner";
package/src/app/page.tsx CHANGED
@@ -41,7 +41,7 @@ export default function home_page() {
41
41
  <SidebarMenuItem className="cls_home_sidebar_test_login_item">
42
42
  <SidebarMenuButton asChild>
43
43
  <Link
44
- href="/login"
44
+ href="/hazo_auth/login"
45
45
  className="cls_home_sidebar_test_login_link flex items-center gap-2"
46
46
  aria-label="Test login layout component"
47
47
  >
@@ -53,7 +53,7 @@ export default function home_page() {
53
53
  <SidebarMenuItem className="cls_home_sidebar_test_register_item">
54
54
  <SidebarMenuButton asChild>
55
55
  <Link
56
- href="/register"
56
+ href="/hazo_auth/register"
57
57
  className="cls_home_sidebar_test_register_link flex items-center gap-2"
58
58
  aria-label="Test register layout component"
59
59
  >
@@ -65,7 +65,7 @@ export default function home_page() {
65
65
  <SidebarMenuItem className="cls_home_sidebar_test_forgot_password_item">
66
66
  <SidebarMenuButton asChild>
67
67
  <Link
68
- href="/forgot_password"
68
+ href="/hazo_auth/forgot_password"
69
69
  className="cls_home_sidebar_test_forgot_password_link flex items-center gap-2"
70
70
  aria-label="Test forgot password layout component"
71
71
  >
@@ -77,7 +77,7 @@ export default function home_page() {
77
77
  <SidebarMenuItem className="cls_home_sidebar_test_email_verification_item">
78
78
  <SidebarMenuButton asChild>
79
79
  <Link
80
- href="/verify_email"
80
+ href="/hazo_auth/verify_email"
81
81
  className="cls_home_sidebar_test_email_verification_link flex items-center gap-2"
82
82
  aria-label="Test email verification layout component"
83
83
  >
@@ -47,7 +47,7 @@ const buildInitialValues = (initialEmail?: string): EmailVerificationFormValues
47
47
  export const use_email_verification = <TClient,>({
48
48
  dataClient,
49
49
  redirectDelay = 5,
50
- loginPath = "/login",
50
+ loginPath = "/hazo_auth/login",
51
51
  }: UseEmailVerificationParams<TClient>): UseEmailVerificationResult => {
52
52
  const router = useRouter();
53
53
  const searchParams = useSearchParams();
@@ -91,7 +91,7 @@ export const use_email_verification = <TClient,>({
91
91
  setErrorMessage(undefined);
92
92
 
93
93
  try {
94
- const response = await fetch(`/api/auth/verify_email?token=${encodeURIComponent(token)}`, {
94
+ const response = await fetch(`/api/hazo_auth/verify_email?token=${encodeURIComponent(token)}`, {
95
95
  method: "GET",
96
96
  });
97
97
 
@@ -131,7 +131,7 @@ export const use_email_verification = <TClient,>({
131
131
 
132
132
  // Try to extract email from error response if available
133
133
  try {
134
- const response = await fetch(`/api/auth/verify_email?token=${encodeURIComponent(token)}`, {
134
+ const response = await fetch(`/api/hazo_auth/verify_email?token=${encodeURIComponent(token)}`, {
135
135
  method: "GET",
136
136
  });
137
137
  const data = await response.json();
@@ -221,7 +221,7 @@ export const use_email_verification = <TClient,>({
221
221
  setErrors({});
222
222
 
223
223
  try {
224
- const response = await fetch("/api/auth/resend_verification", {
224
+ const response = await fetch("/api/hazo_auth/resend_verification", {
225
225
  method: "POST",
226
226
  headers: {
227
227
  "Content-Type": "application/json",
@@ -69,7 +69,7 @@ export default function email_verification_layout<TClient>({
69
69
  success_labels,
70
70
  error_labels,
71
71
  redirect_delay = 5,
72
- login_path = "/login",
72
+ login_path = "/hazo_auth/login",
73
73
  data_client,
74
74
  already_logged_in_message,
75
75
  showLogoutButton = true,
@@ -113,7 +113,7 @@ export const use_forgot_password_form = <TClient,>({
113
113
  setErrors({});
114
114
 
115
115
  try {
116
- const response = await fetch("/api/auth/forgot_password", {
116
+ const response = await fetch("/api/hazo_auth/forgot_password", {
117
117
  method: "POST",
118
118
  headers: {
119
119
  "Content-Type": "application/json",
@@ -181,7 +181,7 @@ export const use_login_form = <TClient,>({
181
181
  setClientIp(currentIp);
182
182
 
183
183
  // Attempt login via API route
184
- const response = await fetch("/api/auth/login", {
184
+ const response = await fetch("/api/hazo_auth/login", {
185
185
  method: "POST",
186
186
  headers: {
187
187
  "Content-Type": "application/json",
@@ -202,7 +202,7 @@ export const use_login_form = <TClient,>({
202
202
  const messageParam = encodeURIComponent(
203
203
  "Your email address has not been verified. Please verify your email to continue."
204
204
  );
205
- router.push(`/verify_email?email=${emailParam}&message=${messageParam}`);
205
+ router.push(`/hazo_auth/verify_email?email=${emailParam}&message=${messageParam}`);
206
206
  return;
207
207
  }
208
208
 
@@ -188,7 +188,7 @@ export function ProfilePictureDialog({
188
188
  const formData = new FormData();
189
189
  formData.append("file", file);
190
190
 
191
- const response = await fetch("/api/auth/upload_profile_picture", {
191
+ const response = await fetch("/api/hazo_auth/upload_profile_picture", {
192
192
  method: "POST",
193
193
  credentials: "include",
194
194
  body: formData,
@@ -57,7 +57,7 @@ export function ProfilePictureLibraryTab({
57
57
  const loadCategories = async () => {
58
58
  setLoadingCategories(true);
59
59
  try {
60
- const response = await fetch("/api/auth/library_photos");
60
+ const response = await fetch("/api/hazo_auth/library_photos");
61
61
  const data = await response.json();
62
62
  if (data.success && data.categories) {
63
63
  setCategories(data.categories);
@@ -99,7 +99,7 @@ export function ProfilePictureLibraryTab({
99
99
  const loadPhotos = async () => {
100
100
  setLoadingPhotos(true);
101
101
  try {
102
- const response = await fetch(`/api/auth/library_photos?category=${encodeURIComponent(selectedCategory)}`);
102
+ const response = await fetch(`/api/hazo_auth/library_photos?category=${encodeURIComponent(selectedCategory)}`);
103
103
  const data = await response.json();
104
104
  if (data.success && data.photos) {
105
105
  setPhotos(data.photos);
@@ -134,7 +134,7 @@ export function use_my_settings({
134
134
  }
135
135
 
136
136
  try {
137
- const response = await fetch("/api/auth/update_user", {
137
+ const response = await fetch("/api/hazo_auth/update_user", {
138
138
  method: "PATCH",
139
139
  headers: {
140
140
  "Content-Type": "application/json",
@@ -171,7 +171,7 @@ export function use_my_settings({
171
171
  }
172
172
 
173
173
  try {
174
- const response = await fetch("/api/auth/update_user", {
174
+ const response = await fetch("/api/hazo_auth/update_user", {
175
175
  method: "PATCH",
176
176
  headers: {
177
177
  "Content-Type": "application/json",
@@ -304,7 +304,7 @@ export function use_my_settings({
304
304
  }
305
305
 
306
306
  try {
307
- const response = await fetch("/api/auth/change_password", {
307
+ const response = await fetch("/api/hazo_auth/change_password", {
308
308
  method: "POST",
309
309
  headers: {
310
310
  "Content-Type": "application/json",
@@ -371,7 +371,7 @@ export function use_my_settings({
371
371
  */
372
372
  const handleProfilePictureSave = useCallback(async (profilePictureUrl: string, profileSource: "upload" | "library" | "gravatar") => {
373
373
  try {
374
- const response = await fetch("/api/auth/update_user", {
374
+ const response = await fetch("/api/hazo_auth/update_user", {
375
375
  method: "PATCH",
376
376
  headers: {
377
377
  "Content-Type": "application/json",
@@ -408,7 +408,7 @@ export function use_my_settings({
408
408
  */
409
409
  const handleProfilePictureRemove = useCallback(async () => {
410
410
  try {
411
- const response = await fetch("/api/auth/remove_profile_picture", {
411
+ const response = await fetch("/api/hazo_auth/remove_profile_picture", {
412
412
  method: "DELETE",
413
413
  headers: {
414
414
  "Content-Type": "application/json",
@@ -94,7 +94,7 @@ export default function my_settings_layout({
94
94
  userFields,
95
95
  unauthorizedMessage = "You must be logged in to access this page.",
96
96
  loginButtonLabel = "Go to login",
97
- loginPath = "/login",
97
+ loginPath = "/hazo_auth/login",
98
98
  heading = "Account Settings",
99
99
  subHeading = "Manage your profile, password, and email preferences.",
100
100
  profilePhotoLabel = "Profile Photo",
@@ -194,7 +194,7 @@ export const use_register_form = <TClient,>({
194
194
  setErrors({});
195
195
 
196
196
  try {
197
- const response = await fetch("/api/auth/register", {
197
+ const response = await fetch("/api/hazo_auth/register", {
198
198
  method: "POST",
199
199
  headers: {
200
200
  "Content-Type": "application/json",
@@ -54,7 +54,7 @@ const buildInitialValues = (): ResetPasswordFormValues => ({
54
54
  export const use_reset_password_form = <TClient,>({
55
55
  passwordRequirements,
56
56
  dataClient,
57
- loginPath = "/login",
57
+ loginPath = "/hazo_auth/login",
58
58
  }: UseResetPasswordFormParams<TClient>): UseResetPasswordFormResult => {
59
59
  const router = useRouter();
60
60
  const searchParams = useSearchParams();
@@ -85,7 +85,7 @@ export const use_reset_password_form = <TClient,>({
85
85
  setTokenError(null);
86
86
 
87
87
  try {
88
- const response = await fetch(`/api/auth/validate_reset_token?token=${encodeURIComponent(tokenParam)}`, {
88
+ const response = await fetch(`/api/hazo_auth/validate_reset_token?token=${encodeURIComponent(tokenParam)}`, {
89
89
  method: "GET",
90
90
  });
91
91
 
@@ -217,7 +217,7 @@ export const use_reset_password_form = <TClient,>({
217
217
  setIsSubmitting(true);
218
218
 
219
219
  try {
220
- const response = await fetch("/api/auth/reset_password", {
220
+ const response = await fetch("/api/hazo_auth/reset_password", {
221
221
  method: "POST",
222
222
  headers: {
223
223
  "Content-Type": "application/json",
@@ -75,8 +75,8 @@ export default function reset_password_layout<TClient>({
75
75
  returnHomePath = "/",
76
76
  errorMessage = "Reset password link invalid or has expired. Please go to Reset Password page to get a new link.",
77
77
  successMessage = "Password reset successfully. Redirecting to login...",
78
- loginPath = "/login",
79
- forgotPasswordPath = "/forgot_password",
78
+ loginPath = "/hazo_auth/login",
79
+ forgotPasswordPath = "/hazo_auth/forgot_password",
80
80
  }: ResetPasswordLayoutProps<TClient>) {
81
81
  const fieldDefinitions = createResetPasswordFieldDefinitions(field_overrides);
82
82
  const resolvedLabels = resolveResetPasswordLabels(labels);
@@ -30,7 +30,7 @@ export function LogoutButton({
30
30
  setIsLoggingOut(true);
31
31
 
32
32
  try {
33
- const response = await fetch("/api/auth/logout", {
33
+ const response = await fetch("/api/hazo_auth/logout", {
34
34
  method: "POST",
35
35
  headers: {
36
36
  "Content-Type": "application/json",
@@ -47,10 +47,10 @@ export function ProfilePicMenu({
47
47
  show_single_button = false,
48
48
  sign_up_label = "Sign Up",
49
49
  sign_in_label = "Sign In",
50
- register_path = "/register",
51
- login_path = "/login",
52
- settings_path = "/my_settings",
53
- logout_path = "/api/auth/logout",
50
+ register_path = "/hazo_auth/register",
51
+ login_path = "/hazo_auth/login",
52
+ settings_path = "/hazo_auth/my_settings",
53
+ logout_path = "/api/hazo_auth/logout",
54
54
  custom_menu_items = [],
55
55
  className,
56
56
  avatar_size = "default",
@@ -17,7 +17,7 @@ import {
17
17
  SidebarTrigger,
18
18
  SidebarInset,
19
19
  } from "@/components/ui/sidebar";
20
- import { LogIn, UserPlus, BookOpen, ExternalLink, Database, KeyRound, MailCheck, Key, Settings } from "lucide-react";
20
+ import { LogIn, UserPlus, BookOpen, ExternalLink, Database, KeyRound, MailCheck, Key, Settings, User } from "lucide-react";
21
21
  import { use_auth_status } from "@/components/layouts/shared/hooks/use_auth_status";
22
22
  import { ProfilePicMenu } from "@/components/layouts/shared/components/profile_pic_menu";
23
23
 
@@ -50,7 +50,7 @@ export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
50
50
  <SidebarMenuItem className="cls_sidebar_layout_test_login_item">
51
51
  <SidebarMenuButton asChild>
52
52
  <Link
53
- href="/login"
53
+ href="/hazo_auth/login"
54
54
  className="cls_sidebar_layout_test_login_link flex items-center gap-2"
55
55
  aria-label="Test login layout component"
56
56
  >
@@ -62,7 +62,7 @@ export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
62
62
  <SidebarMenuItem className="cls_sidebar_layout_test_register_item">
63
63
  <SidebarMenuButton asChild>
64
64
  <Link
65
- href="/register"
65
+ href="/hazo_auth/register"
66
66
  className="cls_sidebar_layout_test_register_link flex items-center gap-2"
67
67
  aria-label="Test register layout component"
68
68
  >
@@ -74,7 +74,7 @@ export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
74
74
  <SidebarMenuItem className="cls_sidebar_layout_test_forgot_password_item">
75
75
  <SidebarMenuButton asChild>
76
76
  <Link
77
- href="/forgot_password"
77
+ href="/hazo_auth/forgot_password"
78
78
  className="cls_sidebar_layout_test_forgot_password_link flex items-center gap-2"
79
79
  aria-label="Test forgot password layout component"
80
80
  >
@@ -86,7 +86,7 @@ export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
86
86
  <SidebarMenuItem className="cls_sidebar_layout_test_reset_password_item">
87
87
  <SidebarMenuButton asChild>
88
88
  <Link
89
- href="/reset_password"
89
+ href="/hazo_auth/reset_password"
90
90
  className="cls_sidebar_layout_test_reset_password_link flex items-center gap-2"
91
91
  aria-label="Test reset password layout component"
92
92
  >
@@ -98,7 +98,7 @@ export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
98
98
  <SidebarMenuItem className="cls_sidebar_layout_test_email_verification_item">
99
99
  <SidebarMenuButton asChild>
100
100
  <Link
101
- href="/verify_email"
101
+ href="/hazo_auth/verify_email"
102
102
  className="cls_sidebar_layout_test_email_verification_link flex items-center gap-2"
103
103
  aria-label="Test email verification layout component"
104
104
  >
@@ -119,6 +119,18 @@ export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
119
119
  </Link>
120
120
  </SidebarMenuButton>
121
121
  </SidebarMenuItem>
122
+ <SidebarMenuItem className="cls_sidebar_layout_user_management_item">
123
+ <SidebarMenuButton asChild>
124
+ <Link
125
+ href="/hazo_auth/user_management"
126
+ className="cls_sidebar_layout_user_management_link flex items-center gap-2"
127
+ aria-label="Open User Management to manage users, roles, and permissions"
128
+ >
129
+ <User className="h-4 w-4" aria-hidden="true" />
130
+ <span>User Management</span>
131
+ </Link>
132
+ </SidebarMenuButton>
133
+ </SidebarMenuItem>
122
134
  </SidebarMenu>
123
135
  </SidebarGroup>
124
136
  {authStatus.authenticated && (
@@ -130,7 +142,7 @@ export function SidebarLayoutWrapper({ children }: SidebarLayoutWrapperProps) {
130
142
  <SidebarMenuItem className="cls_sidebar_layout_my_settings_item">
131
143
  <SidebarMenuButton asChild>
132
144
  <Link
133
- href="/my_settings"
145
+ href="/hazo_auth/my_settings"
134
146
  className="cls_sidebar_layout_my_settings_link flex items-center gap-2"
135
147
  aria-label="Open my settings page"
136
148
  >
@@ -26,7 +26,7 @@ export type UnauthorizedGuardProps = {
26
26
  export function UnauthorizedGuard({
27
27
  message = "You must be logged in to access this page.",
28
28
  loginButtonLabel = "Go to login",
29
- loginPath = "/login",
29
+ loginPath = "/hazo_auth/login",
30
30
  children,
31
31
  }: UnauthorizedGuardProps) {
32
32
  const router = useRouter();
@@ -46,7 +46,7 @@ export function use_auth_status(): AuthStatus {
46
46
  setAuthStatus((prev) => ({ ...prev, loading: true }));
47
47
 
48
48
  try {
49
- const response = await fetch("/api/auth/me", {
49
+ const response = await fetch("/api/hazo_auth/me", {
50
50
  method: "GET",
51
51
  credentials: "include",
52
52
  });
@@ -0,0 +1,158 @@
1
+ // file_description: React hook for hazo_get_auth utility (client-side)
2
+ // section: client_directive
3
+ "use client";
4
+
5
+ // section: imports
6
+ import { useState, useEffect, useCallback } from "react";
7
+ import type { HazoAuthResult } from "@/lib/auth/auth_types";
8
+
9
+ // section: types
10
+
11
+ /**
12
+ * Options for use_hazo_auth hook
13
+ */
14
+ export type UseHazoAuthOptions = {
15
+ /**
16
+ * Array of required permissions to check
17
+ */
18
+ required_permissions?: string[];
19
+ /**
20
+ * If true, throws error when permissions are missing (default: false)
21
+ */
22
+ strict?: boolean;
23
+ /**
24
+ * Skip fetch (for conditional use)
25
+ */
26
+ skip?: boolean;
27
+ };
28
+
29
+ /**
30
+ * Result type for use_hazo_auth hook
31
+ */
32
+ export type UseHazoAuthResult = HazoAuthResult & {
33
+ /**
34
+ * Loading state
35
+ */
36
+ loading: boolean;
37
+ /**
38
+ * Error state
39
+ */
40
+ error: Error | null;
41
+ /**
42
+ * Manual refetch function
43
+ */
44
+ refetch: () => Promise<void>;
45
+ };
46
+
47
+ // section: constants
48
+ const AUTH_STATUS_CHANGE_EVENT = "hazo_auth_status_change";
49
+
50
+ // section: helpers
51
+
52
+ /**
53
+ * Triggers a refresh of hazo_auth status across all components
54
+ * Dispatches a custom event that all use_hazo_auth hooks listen to
55
+ */
56
+ export function trigger_hazo_auth_refresh(): void {
57
+ if (typeof window !== "undefined") {
58
+ window.dispatchEvent(
59
+ new CustomEvent(AUTH_STATUS_CHANGE_EVENT),
60
+ );
61
+ }
62
+ }
63
+
64
+ // section: hook
65
+
66
+ /**
67
+ * React hook for hazo_get_auth utility
68
+ * Fetches authentication status and permissions from /api/auth/get_auth
69
+ * @param options - Optional parameters for permission checking
70
+ * @returns UseHazoAuthResult with auth data, loading state, and refetch function
71
+ */
72
+ export function use_hazo_auth(
73
+ options?: UseHazoAuthOptions,
74
+ ): UseHazoAuthResult {
75
+ const [authResult, setAuthResult] = useState<HazoAuthResult>({
76
+ authenticated: false,
77
+ user: null,
78
+ permissions: [],
79
+ permission_ok: false,
80
+ });
81
+ const [loading, setLoading] = useState<boolean>(true);
82
+ const [error, setError] = useState<Error | null>(null);
83
+
84
+ const fetchAuth = useCallback(async () => {
85
+ if (options?.skip) {
86
+ setLoading(false);
87
+ return;
88
+ }
89
+
90
+ setLoading(true);
91
+ setError(null);
92
+
93
+ try {
94
+ const response = await fetch("/api/hazo_auth/get_auth", {
95
+ method: "POST",
96
+ headers: {
97
+ "Content-Type": "application/json",
98
+ },
99
+ credentials: "include",
100
+ body: JSON.stringify({
101
+ required_permissions: options?.required_permissions,
102
+ strict: options?.strict || false,
103
+ }),
104
+ });
105
+
106
+ if (!response.ok) {
107
+ const error_data = await response.json();
108
+ throw new Error(
109
+ error_data.user_friendly_message ||
110
+ error_data.error ||
111
+ "Failed to fetch authentication status",
112
+ );
113
+ }
114
+
115
+ const data = await response.json();
116
+ setAuthResult(data);
117
+ } catch (err) {
118
+ const error_message =
119
+ err instanceof Error ? err : new Error("Unknown error");
120
+ setError(error_message);
121
+ setAuthResult({
122
+ authenticated: false,
123
+ user: null,
124
+ permissions: [],
125
+ permission_ok: false,
126
+ });
127
+ } finally {
128
+ setLoading(false);
129
+ }
130
+ }, [options?.required_permissions, options?.strict, options?.skip]);
131
+
132
+ useEffect(() => {
133
+ // Fetch auth status on mount
134
+ void fetchAuth();
135
+
136
+ // Listen for auth status change events
137
+ const handleAuthChange = () => {
138
+ void fetchAuth();
139
+ };
140
+
141
+ window.addEventListener(AUTH_STATUS_CHANGE_EVENT, handleAuthChange);
142
+
143
+ return () => {
144
+ window.removeEventListener(
145
+ AUTH_STATUS_CHANGE_EVENT,
146
+ handleAuthChange,
147
+ );
148
+ };
149
+ }, [fetchAuth]);
150
+
151
+ return {
152
+ ...authResult,
153
+ loading,
154
+ error,
155
+ refetch: fetchAuth,
156
+ };
157
+ }
158
+