hazo_auth 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +48 -0
- package/components.json +22 -0
- package/hazo_auth_config.example.ini +414 -0
- package/hazo_notify_config.example.ini +159 -0
- package/instrumentation.ts +32 -0
- package/migrations/001_add_token_type_to_refresh_tokens.sql +14 -0
- package/migrations/002_add_name_to_hazo_users.sql +7 -0
- package/next.config.mjs +55 -0
- package/package.json +114 -0
- package/postcss.config.mjs +8 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/scripts/apply_migration.ts +118 -0
- package/src/app/api/auth/change_password/route.ts +109 -0
- package/src/app/api/auth/forgot_password/route.ts +107 -0
- package/src/app/api/auth/library_photos/route.ts +70 -0
- package/src/app/api/auth/login/route.ts +155 -0
- package/src/app/api/auth/logout/route.ts +62 -0
- package/src/app/api/auth/me/route.ts +47 -0
- package/src/app/api/auth/profile_picture/[filename]/route.ts +67 -0
- package/src/app/api/auth/register/route.ts +106 -0
- package/src/app/api/auth/remove_profile_picture/route.ts +86 -0
- package/src/app/api/auth/resend_verification/route.ts +107 -0
- package/src/app/api/auth/reset_password/route.ts +107 -0
- package/src/app/api/auth/update_user/route.ts +126 -0
- package/src/app/api/auth/upload_profile_picture/route.ts +268 -0
- package/src/app/api/auth/validate_reset_token/route.ts +80 -0
- package/src/app/api/auth/verify_email/route.ts +85 -0
- package/src/app/api/migrations/apply/route.ts +91 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/fonts/GeistMonoVF.woff +0 -0
- package/src/app/fonts/GeistVF.woff +0 -0
- package/src/app/forgot_password/forgot_password_page_client.tsx +60 -0
- package/src/app/forgot_password/page.tsx +24 -0
- package/src/app/globals.css +89 -0
- package/src/app/hazo_connect/api/sqlite/data/route.ts +197 -0
- package/src/app/hazo_connect/api/sqlite/schema/route.ts +35 -0
- package/src/app/hazo_connect/api/sqlite/tables/route.ts +26 -0
- package/src/app/hazo_connect/sqlite_admin/page.tsx +51 -0
- package/src/app/hazo_connect/sqlite_admin/sqlite-admin-client.tsx +947 -0
- package/src/app/layout.tsx +43 -0
- package/src/app/login/login_page_client.tsx +71 -0
- package/src/app/login/page.tsx +26 -0
- package/src/app/my_settings/my_settings_page_client.tsx +120 -0
- package/src/app/my_settings/page.tsx +40 -0
- package/src/app/page.tsx +170 -0
- package/src/app/register/page.tsx +26 -0
- package/src/app/register/register_page_client.tsx +72 -0
- package/src/app/reset_password/page.tsx +29 -0
- package/src/app/reset_password/reset_password_page_client.tsx +81 -0
- package/src/app/verify_email/page.tsx +24 -0
- package/src/app/verify_email/verify_email_page_client.tsx +60 -0
- package/src/components/layouts/email_verification/config/email_verification_field_config.ts +86 -0
- package/src/components/layouts/email_verification/hooks/use_email_verification.ts +291 -0
- package/src/components/layouts/email_verification/index.tsx +297 -0
- package/src/components/layouts/forgot_password/config/forgot_password_field_config.ts +58 -0
- package/src/components/layouts/forgot_password/hooks/use_forgot_password_form.ts +179 -0
- package/src/components/layouts/forgot_password/index.tsx +168 -0
- package/src/components/layouts/login/config/login_field_config.ts +67 -0
- package/src/components/layouts/login/hooks/use_login_form.ts +281 -0
- package/src/components/layouts/login/index.tsx +224 -0
- package/src/components/layouts/my_settings/components/editable_field.tsx +177 -0
- package/src/components/layouts/my_settings/components/password_change_dialog.tsx +301 -0
- package/src/components/layouts/my_settings/components/profile_picture_dialog.tsx +385 -0
- package/src/components/layouts/my_settings/components/profile_picture_display.tsx +66 -0
- package/src/components/layouts/my_settings/components/profile_picture_gravatar_tab.tsx +143 -0
- package/src/components/layouts/my_settings/components/profile_picture_library_tab.tsx +282 -0
- package/src/components/layouts/my_settings/components/profile_picture_upload_tab.tsx +341 -0
- package/src/components/layouts/my_settings/config/my_settings_field_config.ts +61 -0
- package/src/components/layouts/my_settings/hooks/use_my_settings.ts +458 -0
- package/src/components/layouts/my_settings/index.tsx +351 -0
- package/src/components/layouts/register/config/register_field_config.ts +101 -0
- package/src/components/layouts/register/hooks/use_register_form.ts +272 -0
- package/src/components/layouts/register/index.tsx +208 -0
- package/src/components/layouts/reset_password/config/reset_password_field_config.ts +86 -0
- package/src/components/layouts/reset_password/hooks/use_reset_password_form.ts +276 -0
- package/src/components/layouts/reset_password/index.tsx +294 -0
- package/src/components/layouts/shared/components/already_logged_in_guard.tsx +95 -0
- package/src/components/layouts/shared/components/field_error_message.tsx +29 -0
- package/src/components/layouts/shared/components/form_action_buttons.tsx +64 -0
- package/src/components/layouts/shared/components/form_field_wrapper.tsx +44 -0
- package/src/components/layouts/shared/components/form_header.tsx +36 -0
- package/src/components/layouts/shared/components/logout_button.tsx +76 -0
- package/src/components/layouts/shared/components/password_field.tsx +72 -0
- package/src/components/layouts/shared/components/sidebar_layout_wrapper.tsx +264 -0
- package/src/components/layouts/shared/components/two_column_auth_layout.tsx +44 -0
- package/src/components/layouts/shared/components/unauthorized_guard.tsx +78 -0
- package/src/components/layouts/shared/components/visual_panel.tsx +41 -0
- package/src/components/layouts/shared/config/layout_customization.ts +95 -0
- package/src/components/layouts/shared/data/layout_data_client.ts +19 -0
- package/src/components/layouts/shared/hooks/use_auth_status.ts +103 -0
- package/src/components/layouts/shared/utils/ip_address.ts +37 -0
- package/src/components/layouts/shared/utils/validation.ts +66 -0
- package/src/components/ui/avatar.tsx +50 -0
- package/src/components/ui/button.tsx +57 -0
- package/src/components/ui/dialog.tsx +122 -0
- package/src/components/ui/hazo_ui_tooltip.tsx +67 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/separator.tsx +31 -0
- package/src/components/ui/sheet.tsx +139 -0
- package/src/components/ui/sidebar.tsx +773 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/ui/sonner.tsx +31 -0
- package/src/components/ui/switch.tsx +29 -0
- package/src/components/ui/tabs.tsx +55 -0
- package/src/components/ui/tooltip.tsx +32 -0
- package/src/components/ui/vertical-tabs.tsx +59 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/lib/already_logged_in_config.server.ts +46 -0
- package/src/lib/app_logger.ts +24 -0
- package/src/lib/auth/auth_utils.server.ts +196 -0
- package/src/lib/auth/server_auth.ts +88 -0
- package/src/lib/config/config_loader.server.ts +149 -0
- package/src/lib/email_verification_config.server.ts +32 -0
- package/src/lib/file_types_config.server.ts +25 -0
- package/src/lib/forgot_password_config.server.ts +32 -0
- package/src/lib/hazo_connect_instance.server.ts +77 -0
- package/src/lib/hazo_connect_setup.server.ts +181 -0
- package/src/lib/hazo_connect_setup.ts +54 -0
- package/src/lib/login_config.server.ts +46 -0
- package/src/lib/messages_config.server.ts +45 -0
- package/src/lib/migrations/apply_migration.ts +105 -0
- package/src/lib/my_settings_config.server.ts +135 -0
- package/src/lib/password_requirements_config.server.ts +39 -0
- package/src/lib/profile_picture_config.server.ts +56 -0
- package/src/lib/register_config.server.ts +57 -0
- package/src/lib/reset_password_config.server.ts +75 -0
- package/src/lib/services/email_service.ts +581 -0
- package/src/lib/services/email_verification_service.ts +264 -0
- package/src/lib/services/login_service.ts +118 -0
- package/src/lib/services/password_change_service.ts +154 -0
- package/src/lib/services/password_reset_service.ts +405 -0
- package/src/lib/services/profile_picture_remove_service.ts +120 -0
- package/src/lib/services/profile_picture_service.ts +215 -0
- package/src/lib/services/profile_picture_source_mapper.ts +62 -0
- package/src/lib/services/registration_service.ts +163 -0
- package/src/lib/services/token_service.ts +240 -0
- package/src/lib/services/user_update_service.ts +128 -0
- package/src/lib/ui_sizes_config.server.ts +37 -0
- package/src/lib/user_fields_config.server.ts +31 -0
- package/src/lib/utils/api_route_helpers.ts +60 -0
- package/src/lib/utils.ts +11 -0
- package/src/middleware.ts +91 -0
- package/src/server/config/config_loader.ts +496 -0
- package/src/server/index.ts +38 -0
- package/src/server/logging/logger_service.ts +56 -0
- package/src/server/routes/root_router.ts +16 -0
- package/src/server/server.ts +28 -0
- package/src/server/types/app_types.ts +74 -0
- package/src/server/types/express.d.ts +15 -0
- package/src/stories/email_verification_layout.stories.tsx +137 -0
- package/src/stories/forgot_password_layout.stories.tsx +85 -0
- package/src/stories/login_layout.stories.tsx +85 -0
- package/src/stories/project_overview.stories.tsx +33 -0
- package/src/stories/register_layout.stories.tsx +107 -0
- package/tailwind.config.ts +77 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// file_description: service for updating user profile information using hazo_connect
|
|
2
|
+
// section: imports
|
|
3
|
+
import type { HazoConnectAdapter } from "hazo_connect";
|
|
4
|
+
import { createCrudService } from "hazo_connect/server";
|
|
5
|
+
import { map_ui_source_to_db, type ProfilePictureSourceUI } from "./profile_picture_source_mapper";
|
|
6
|
+
|
|
7
|
+
// section: types
|
|
8
|
+
export type UserUpdateData = {
|
|
9
|
+
name?: string;
|
|
10
|
+
email?: string;
|
|
11
|
+
profile_picture_url?: string;
|
|
12
|
+
profile_source?: ProfilePictureSourceUI;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type UserUpdateResult = {
|
|
16
|
+
success: boolean;
|
|
17
|
+
email_changed?: boolean;
|
|
18
|
+
error?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// section: helpers
|
|
22
|
+
/**
|
|
23
|
+
* Updates user profile information (name, email)
|
|
24
|
+
* If email is changed, sets email_verified to false
|
|
25
|
+
* @param adapter - The hazo_connect adapter instance
|
|
26
|
+
* @param user_id - The user ID to update
|
|
27
|
+
* @param data - User update data (name, email)
|
|
28
|
+
* @returns User update result with success status, email_changed flag, or error
|
|
29
|
+
*/
|
|
30
|
+
export async function update_user_profile(
|
|
31
|
+
adapter: HazoConnectAdapter,
|
|
32
|
+
user_id: string,
|
|
33
|
+
data: UserUpdateData,
|
|
34
|
+
): Promise<UserUpdateResult> {
|
|
35
|
+
try {
|
|
36
|
+
const { name, email } = data;
|
|
37
|
+
|
|
38
|
+
// Create CRUD service for hazo_users table
|
|
39
|
+
const users_service = createCrudService(adapter, "hazo_users");
|
|
40
|
+
|
|
41
|
+
// Get current user data
|
|
42
|
+
const users = await users_service.findBy({
|
|
43
|
+
id: user_id,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!Array.isArray(users) || users.length === 0) {
|
|
47
|
+
return {
|
|
48
|
+
success: false,
|
|
49
|
+
error: "User not found",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const current_user = users[0];
|
|
54
|
+
const current_email = current_user.email_address as string;
|
|
55
|
+
const email_changed = email !== undefined && email !== current_email;
|
|
56
|
+
|
|
57
|
+
// If email is being changed, check if new email already exists
|
|
58
|
+
if (email_changed) {
|
|
59
|
+
const existing_users = await users_service.findBy({
|
|
60
|
+
email_address: email,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (Array.isArray(existing_users) && existing_users.length > 0) {
|
|
64
|
+
// Check if it's the same user
|
|
65
|
+
const existing_user = existing_users[0];
|
|
66
|
+
if (existing_user.id !== user_id) {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
error: "Email address already registered",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Validate email format
|
|
75
|
+
const email_regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
76
|
+
if (!email_regex.test(email)) {
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
error: "Invalid email address format",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Prepare update data
|
|
85
|
+
const update_data: Record<string, unknown> = {
|
|
86
|
+
changed_at: new Date().toISOString(),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
if (name !== undefined) {
|
|
90
|
+
update_data.name = name;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (email !== undefined) {
|
|
94
|
+
update_data.email_address = email;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (data.profile_picture_url !== undefined) {
|
|
98
|
+
update_data.profile_picture_url = data.profile_picture_url;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (data.profile_source !== undefined) {
|
|
102
|
+
// Map UI source value to database enum value
|
|
103
|
+
update_data.profile_source = map_ui_source_to_db(data.profile_source);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// If email changed, set email_verified to false
|
|
107
|
+
if (email_changed) {
|
|
108
|
+
update_data.email_verified = false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Update user in database
|
|
112
|
+
await users_service.updateById(user_id, update_data);
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
success: true,
|
|
116
|
+
email_changed,
|
|
117
|
+
};
|
|
118
|
+
} catch (error) {
|
|
119
|
+
const error_message =
|
|
120
|
+
error instanceof Error ? error.message : "Unknown error";
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
error: error_message,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// file_description: server-only helper to read UI size configuration from hazo_auth_config.ini
|
|
2
|
+
// section: imports
|
|
3
|
+
import { get_config_number } from "./config/config_loader.server";
|
|
4
|
+
|
|
5
|
+
// section: types
|
|
6
|
+
export type UISizesConfig = {
|
|
7
|
+
gravatar_size: number;
|
|
8
|
+
profile_picture_size: number;
|
|
9
|
+
tooltip_icon_size_default: number;
|
|
10
|
+
tooltip_icon_size_small: number;
|
|
11
|
+
library_photo_grid_columns: number;
|
|
12
|
+
library_photo_preview_size: number;
|
|
13
|
+
image_compression_max_dimension: number;
|
|
14
|
+
upload_file_hard_limit_bytes: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// section: helpers
|
|
18
|
+
/**
|
|
19
|
+
* Reads UI size configuration from hazo_auth_config.ini file
|
|
20
|
+
* Falls back to defaults if hazo_auth_config.ini is not found or section is missing
|
|
21
|
+
* @returns UI sizes configuration options
|
|
22
|
+
*/
|
|
23
|
+
export function get_ui_sizes_config(): UISizesConfig {
|
|
24
|
+
const section = "hazo_auth__ui_sizes";
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
gravatar_size: get_config_number(section, "gravatar_size", 200),
|
|
28
|
+
profile_picture_size: get_config_number(section, "profile_picture_size", 200),
|
|
29
|
+
tooltip_icon_size_default: get_config_number(section, "tooltip_icon_size_default", 16),
|
|
30
|
+
tooltip_icon_size_small: get_config_number(section, "tooltip_icon_size_small", 14),
|
|
31
|
+
library_photo_grid_columns: get_config_number(section, "library_photo_grid_columns", 4),
|
|
32
|
+
library_photo_preview_size: get_config_number(section, "library_photo_preview_size", 200),
|
|
33
|
+
image_compression_max_dimension: get_config_number(section, "image_compression_max_dimension", 200),
|
|
34
|
+
upload_file_hard_limit_bytes: get_config_number(section, "upload_file_hard_limit_bytes", 10485760), // 10MB
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// file_description: server-only helper to read shared user fields configuration from hazo_auth_config.ini
|
|
2
|
+
// section: imports
|
|
3
|
+
import { get_config_boolean } from "./config/config_loader.server";
|
|
4
|
+
|
|
5
|
+
// section: types
|
|
6
|
+
export type UserFieldsConfig = {
|
|
7
|
+
show_name_field: boolean;
|
|
8
|
+
show_email_field: boolean;
|
|
9
|
+
show_password_field: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// section: helpers
|
|
13
|
+
/**
|
|
14
|
+
* Reads shared user fields configuration from hazo_auth_config.ini file
|
|
15
|
+
* Falls back to defaults if hazo_auth_config.ini is not found or section is missing
|
|
16
|
+
* This configuration is used by register and my_settings layouts
|
|
17
|
+
* @returns User fields configuration options
|
|
18
|
+
*/
|
|
19
|
+
export function get_user_fields_config(): UserFieldsConfig {
|
|
20
|
+
// Read field visibility with defaults (all true by default)
|
|
21
|
+
const show_name_field = get_config_boolean("hazo_auth__user_fields", "show_name_field", true);
|
|
22
|
+
const show_email_field = get_config_boolean("hazo_auth__user_fields", "show_email_field", true);
|
|
23
|
+
const show_password_field = get_config_boolean("hazo_auth__user_fields", "show_password_field", true);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
show_name_field,
|
|
27
|
+
show_email_field,
|
|
28
|
+
show_password_field,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// file_description: shared helper functions for API routes to get filename and line number
|
|
2
|
+
// section: helpers
|
|
3
|
+
/**
|
|
4
|
+
* Gets the filename from the call stack
|
|
5
|
+
* This is a simplified version that extracts the filename from the error stack
|
|
6
|
+
* @returns Filename or "route.ts" as default
|
|
7
|
+
*/
|
|
8
|
+
export function get_filename(): string {
|
|
9
|
+
try {
|
|
10
|
+
const stack = new Error().stack;
|
|
11
|
+
if (!stack) {
|
|
12
|
+
return "route.ts";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Parse stack trace to find the caller's file
|
|
16
|
+
const lines = stack.split("\n");
|
|
17
|
+
// Skip Error line and get_filename line, get the actual caller
|
|
18
|
+
for (let i = 2; i < lines.length; i++) {
|
|
19
|
+
const line = lines[i];
|
|
20
|
+
// Match file paths in stack trace (e.g., "at /path/to/file.ts:123:45")
|
|
21
|
+
const match = line.match(/([^/\\]+\.tsx?):(\d+):(\d+)/);
|
|
22
|
+
if (match) {
|
|
23
|
+
return match[1];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return "route.ts";
|
|
27
|
+
} catch {
|
|
28
|
+
return "route.ts";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Gets the line number from the call stack
|
|
34
|
+
* This is a simplified version that extracts the line number from the error stack
|
|
35
|
+
* @returns Line number or 0
|
|
36
|
+
*/
|
|
37
|
+
export function get_line_number(): number {
|
|
38
|
+
try {
|
|
39
|
+
const stack = new Error().stack;
|
|
40
|
+
if (!stack) {
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Parse stack trace to find the caller's line number
|
|
45
|
+
const lines = stack.split("\n");
|
|
46
|
+
// Skip Error line and get_line_number line, get the actual caller
|
|
47
|
+
for (let i = 2; i < lines.length; i++) {
|
|
48
|
+
const line = lines[i];
|
|
49
|
+
// Match line numbers in stack trace (e.g., "at /path/to/file.ts:123:45")
|
|
50
|
+
const match = line.match(/([^/\\]+\.tsx?):(\d+):(\d+)/);
|
|
51
|
+
if (match) {
|
|
52
|
+
return parseInt(match[2], 10) || 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return 0;
|
|
56
|
+
} catch {
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// file_description: provide shared utility helpers for the ui_component project
|
|
2
|
+
import { clsx, type ClassValue } from "clsx";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
|
|
5
|
+
// section: tailwind_merge_helper
|
|
6
|
+
export function merge_class_names(...inputs: ClassValue[]) {
|
|
7
|
+
return twMerge(clsx(inputs));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// section: shadcn_compatibility_helper
|
|
11
|
+
export const cn = (...inputs: ClassValue[]) => merge_class_names(...inputs);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// file_description: Next.js middleware for protecting routes based on authentication
|
|
2
|
+
// Note: Middleware runs in Edge Runtime, so it cannot use Node.js APIs (like SQLite)
|
|
3
|
+
// This middleware only checks for cookies - actual database validation happens in API routes
|
|
4
|
+
// section: imports
|
|
5
|
+
import { NextResponse } from "next/server";
|
|
6
|
+
import type { NextRequest } from "next/server";
|
|
7
|
+
|
|
8
|
+
// section: helpers
|
|
9
|
+
/**
|
|
10
|
+
* Checks if authentication cookies exist (lightweight check for Edge Runtime)
|
|
11
|
+
* Does not validate against database - that happens in API routes
|
|
12
|
+
* @param request - NextRequest object
|
|
13
|
+
* @returns true if cookies exist, false otherwise
|
|
14
|
+
*/
|
|
15
|
+
function has_auth_cookies(request: NextRequest): boolean {
|
|
16
|
+
const user_id = request.cookies.get("hazo_auth_user_id")?.value;
|
|
17
|
+
const user_email = request.cookies.get("hazo_auth_user_email")?.value;
|
|
18
|
+
|
|
19
|
+
return !!(user_id && user_email);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// section: middleware
|
|
23
|
+
/**
|
|
24
|
+
* Next.js middleware function that runs on every request
|
|
25
|
+
* Protects routes by checking for authentication cookies
|
|
26
|
+
*
|
|
27
|
+
* Note: This middleware runs in Edge Runtime and cannot access Node.js APIs (like SQLite)
|
|
28
|
+
* It only checks if cookies exist - actual database validation happens in API routes
|
|
29
|
+
*
|
|
30
|
+
* Public routes (login, register, etc.) are allowed without authentication
|
|
31
|
+
* Protected routes require authentication cookies and redirect to login if not present
|
|
32
|
+
*/
|
|
33
|
+
export async function middleware(request: NextRequest) {
|
|
34
|
+
const pathname = request.nextUrl.pathname;
|
|
35
|
+
|
|
36
|
+
// Public routes that don't require authentication
|
|
37
|
+
const public_routes = [
|
|
38
|
+
"/login",
|
|
39
|
+
"/register",
|
|
40
|
+
"/forgot_password",
|
|
41
|
+
"/reset_password",
|
|
42
|
+
"/verify_email",
|
|
43
|
+
"/api/auth/login",
|
|
44
|
+
"/api/auth/register",
|
|
45
|
+
"/api/auth/forgot_password",
|
|
46
|
+
"/api/auth/reset_password",
|
|
47
|
+
"/api/auth/verify_email",
|
|
48
|
+
"/api/auth/validate_reset_token",
|
|
49
|
+
"/api/auth/me", // Allow /api/auth/me to be public (returns authenticated: false if not logged in)
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
// Check if route is public
|
|
53
|
+
const is_public_route = public_routes.some((route) =>
|
|
54
|
+
pathname.startsWith(route)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Allow public routes
|
|
58
|
+
if (is_public_route) {
|
|
59
|
+
return NextResponse.next();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Check if authentication cookies exist (lightweight check)
|
|
63
|
+
// Note: This doesn't validate against database - API routes will do that
|
|
64
|
+
const has_cookies = has_auth_cookies(request);
|
|
65
|
+
|
|
66
|
+
if (!has_cookies) {
|
|
67
|
+
// Redirect to login if no cookies (not authenticated)
|
|
68
|
+
const login_url = new URL("/login", request.url);
|
|
69
|
+
login_url.searchParams.set("redirect", pathname);
|
|
70
|
+
return NextResponse.redirect(login_url);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Allow requests with cookies (actual validation happens in API routes)
|
|
74
|
+
return NextResponse.next();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// section: config
|
|
78
|
+
export const config = {
|
|
79
|
+
matcher: [
|
|
80
|
+
/*
|
|
81
|
+
* Match all request paths except for the ones starting with:
|
|
82
|
+
* - _next/static (static files)
|
|
83
|
+
* - _next/image (image optimization files)
|
|
84
|
+
* - favicon.ico (favicon file)
|
|
85
|
+
* - public (public files)
|
|
86
|
+
* - api/auth/me (public endpoint)
|
|
87
|
+
*/
|
|
88
|
+
"/((?!_next/static|_next/image|favicon.ico|public).*)",
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
|