hazo_auth 5.1.33 → 5.1.35

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/README.md CHANGED
@@ -251,7 +251,7 @@ export default function Page() {
251
251
  | Page | Import | Description |
252
252
  |------|--------|-------------|
253
253
  | **LoginPage** | `hazo_auth/pages/login` | Login form with forgot password link |
254
- | **RegisterPage** | `hazo_auth/pages/register` | Registration with password validation |
254
+ | **RegisterPage** | `hazo_auth/pages/register` | Registration with password validation and OAuth support |
255
255
  | **ForgotPasswordPage** | `hazo_auth/pages/forgot_password` | Request password reset email |
256
256
  | **ResetPasswordPage** | `hazo_auth/pages/reset_password` | Set new password with token |
257
257
  | **VerifyEmailPage** | `hazo_auth/pages/verify_email` | Email verification handler |
@@ -926,7 +926,7 @@ hazo_auth supports Google Sign-In via NextAuth.js v4, allowing users to authenti
926
926
 
927
927
  - **Dual authentication**: Users can have BOTH Google OAuth and email/password login
928
928
  - **Auto-linking**: Automatically links Google login to existing unverified email/password accounts
929
- - **Graceful degradation**: Login page adapts based on enabled authentication methods
929
+ - **Graceful degradation**: Login and register pages adapt based on enabled authentication methods
930
930
  - **Set password feature**: Google-only users can add a password later via My Settings
931
931
  - **Profile data**: Full name and profile picture automatically populated from Google
932
932
 
@@ -1018,6 +1018,7 @@ auto_link_unverified_accounts = true
1018
1018
 
1019
1019
  # Customize button text (optional)
1020
1020
  google_button_text = Continue with Google
1021
+ google_button_text_register = Sign up with Google
1021
1022
  oauth_divider_text = or
1022
1023
 
1023
1024
  # Post-Login Redirect Configuration (v5.1.16+)
@@ -1266,7 +1267,7 @@ export default async function LoginPage() {
1266
1267
 
1267
1268
  **Available Layout Components:**
1268
1269
  - `LoginLayout` - Login form with email/password
1269
- - `RegisterLayout` - Registration form with password requirements
1270
+ - `RegisterLayout` - Registration form with password requirements and OAuth (Google Sign-In)
1270
1271
  - `ForgotPasswordLayout` - Request password reset
1271
1272
  - `ResetPasswordLayout` - Set new password with token
1272
1273
  - `EmailVerificationLayout` - Verify email address
@@ -7,6 +7,7 @@ import { get_config_boolean, get_config_value, read_config_section } from "./con
7
7
  import { get_password_requirements_config } from "./password_requirements_config.server.js";
8
8
  import { get_already_logged_in_config } from "./already_logged_in_config.server.js";
9
9
  import { get_user_fields_config } from "./user_fields_config.server.js";
10
+ import { get_oauth_config } from "./oauth_config.server.js";
10
11
 
11
12
  // Default image path - consuming apps should either:
12
13
  // 1. Configure their own image_src in hazo_auth_config.ini
@@ -33,6 +34,13 @@ export type RegisterConfig = {
33
34
  imageSrc: string;
34
35
  imageAlt: string;
35
36
  imageBackgroundColor: string;
37
+ /** OAuth configuration */
38
+ oauth: {
39
+ enable_google: boolean;
40
+ enable_email_password: boolean;
41
+ google_button_text: string;
42
+ oauth_divider_text: string;
43
+ };
36
44
  };
37
45
 
38
46
  // section: helpers
@@ -89,6 +97,16 @@ export function get_register_config(): RegisterConfig {
89
97
  "#e2e8f0"
90
98
  );
91
99
 
100
+ // Get OAuth configuration (shared with login)
101
+ const oauthConfig = get_oauth_config();
102
+
103
+ // For the register page, default button text to "Sign up with Google" unless overridden
104
+ const registerGoogleButtonText = get_config_value(
105
+ "hazo_auth__oauth",
106
+ "google_button_text_register",
107
+ "Sign up with Google"
108
+ );
109
+
92
110
  return {
93
111
  showNameField,
94
112
  passwordRequirements,
@@ -102,6 +120,12 @@ export function get_register_config(): RegisterConfig {
102
120
  imageSrc,
103
121
  imageAlt,
104
122
  imageBackgroundColor,
123
+ oauth: {
124
+ enable_google: oauthConfig.enable_google,
125
+ enable_email_password: oauthConfig.enable_email_password,
126
+ google_button_text: registerGoogleButtonText,
127
+ oauth_divider_text: oauthConfig.oauth_divider_text,
128
+ },
105
129
  };
106
130
  }
107
131
 
@@ -0,0 +1,81 @@
1
+ // file_description: Shared utility for rewriting request.url behind reverse proxies (Cloudflare Tunnel, nginx, AWS ALB)
2
+ import { NextRequest } from "next/server";
3
+
4
+ /**
5
+ * Detects the public-facing origin from proxy headers.
6
+ * Returns null if not behind a proxy (origins match).
7
+ */
8
+ function detect_public_origin(request_url: URL, headers: Headers): string | null {
9
+ // Priority 1: NEXTAUTH_URL env var (explicitly configured)
10
+ const nextauth_url = process.env.NEXTAUTH_URL;
11
+ if (nextauth_url) {
12
+ try {
13
+ const public_origin = new URL(nextauth_url).origin;
14
+ if (public_origin !== request_url.origin) {
15
+ return public_origin;
16
+ }
17
+ } catch {
18
+ // Invalid NEXTAUTH_URL, fall through
19
+ }
20
+ }
21
+
22
+ // Priority 2: x-forwarded-host header (set by Cloudflare Tunnel, nginx, etc.)
23
+ const forwarded_host = headers.get("x-forwarded-host");
24
+ if (forwarded_host && forwarded_host !== request_url.hostname) {
25
+ const proto = headers.get("x-forwarded-proto") || "https";
26
+ return `${proto}://${forwarded_host}`;
27
+ }
28
+
29
+ // Priority 3: host header (Cloudflare Tunnel also sets this)
30
+ const host_header = headers.get("host");
31
+ if (host_header && host_header !== request_url.host) {
32
+ const proto = headers.get("x-forwarded-proto") || "https";
33
+ return `${proto}://${host_header}`;
34
+ }
35
+
36
+ return null;
37
+ }
38
+
39
+ /**
40
+ * Rewrites request.url to use the public-facing origin when behind a reverse proxy.
41
+ *
42
+ * Behind proxies like Cloudflare Tunnel, Next.js sets request.url to the internal
43
+ * address (e.g., https://localhost:3000). This causes NextResponse.redirect() to
44
+ * resolve Location headers against the internal origin instead of the public domain.
45
+ *
46
+ * Apply this at the TOP of any route handler that uses NextResponse.redirect().
47
+ *
48
+ * @param request - The incoming NextRequest
49
+ * @returns A new NextRequest with corrected URL, or the original if no proxy detected
50
+ */
51
+ export function rewrite_request_for_proxy(request: NextRequest): NextRequest {
52
+ try {
53
+ const request_url = new URL(request.url);
54
+ const public_origin = detect_public_origin(request_url, request.headers);
55
+
56
+ if (!public_origin) {
57
+ return request; // Not behind a proxy, no rewriting needed
58
+ }
59
+
60
+ // Construct corrected URL: public origin + original path + query
61
+ const corrected_url = `${public_origin}${request_url.pathname}${request_url.search}`;
62
+
63
+ // Create new NextRequest with corrected URL, preserving everything else
64
+ return new NextRequest(corrected_url, {
65
+ method: request.method,
66
+ headers: request.headers,
67
+ body: request.body,
68
+ });
69
+ } catch {
70
+ return request;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Gets the public-facing origin for constructing redirect URLs.
76
+ * Use this when you need the origin string but don't need to rewrite the request.
77
+ */
78
+ export function get_public_origin(request: NextRequest): string {
79
+ const request_url = new URL(request.url);
80
+ return detect_public_origin(request_url, request.headers) || request_url.origin;
81
+ }
@@ -1,6 +1,16 @@
1
1
  import { type ButtonPaletteOverrides, type LayoutFieldMapOverrides, type LayoutLabelOverrides, type PasswordRequirementOverrides } from "../shared/config/layout_customization.js";
2
2
  import { type LayoutDataClient } from "../shared/data/layout_data_client.js";
3
3
  import type { StaticImageData } from "next/image";
4
+ export type OAuthLayoutConfig = {
5
+ /** Enable Google OAuth login */
6
+ enable_google: boolean;
7
+ /** Enable traditional email/password login */
8
+ enable_email_password: boolean;
9
+ /** Text displayed on the Google sign-in button */
10
+ google_button_text: string;
11
+ /** Text displayed on the divider between OAuth and email/password form */
12
+ oauth_divider_text: string;
13
+ };
4
14
  export type RegisterLayoutProps<TClient = unknown> = {
5
15
  image_src: string | StaticImageData;
6
16
  image_alt: string;
@@ -19,6 +29,8 @@ export type RegisterLayoutProps<TClient = unknown> = {
19
29
  signInPath?: string;
20
30
  signInLabel?: string;
21
31
  urlOnLogon?: string;
32
+ /** OAuth configuration */
33
+ oauth?: OAuthLayoutConfig;
22
34
  };
23
- export default function register_layout<TClient>({ image_src, image_alt, image_background_color, field_overrides, labels, button_colors, password_requirements, show_name_field, data_client, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, signInPath, signInLabel, urlOnLogon, }: RegisterLayoutProps<TClient>): import("react/jsx-runtime").JSX.Element;
35
+ export default function register_layout<TClient>({ image_src, image_alt, image_background_color, field_overrides, labels, button_colors, password_requirements, show_name_field, data_client, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, signInPath, signInLabel, urlOnLogon, oauth, }: RegisterLayoutProps<TClient>): import("react/jsx-runtime").JSX.Element;
24
36
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/register/index.tsx"],"names":[],"mappings":"AAaA,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,4BAA4B,EAClC,MAAM,uCAAuC,CAAC;AAY/C,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGlD,MAAM,MAAM,mBAAmB,CAAC,OAAO,GAAG,OAAO,IAAI;IACnD,SAAS,EAAE,MAAM,GAAG,eAAe,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,eAAe,CAAC,EAAE,uBAAuB,CAAC;IAC1C,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC,qBAAqB,CAAC,EAAE,4BAA4B,CAAC;IACrD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACvC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAYF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,OAAO,EAAE,EAC/C,SAAS,EACT,SAAS,EACT,sBAAkC,EAClC,eAAe,EACf,MAAM,EACN,aAAa,EACb,qBAAqB,EACrB,eAAsB,EACtB,WAAW,EACX,sBAAoD,EACpD,gBAAuB,EACvB,oBAA4B,EAC5B,qBAAqC,EACrC,cAAoB,EACpB,UAA+B,EAC/B,WAAuB,EACvB,UAAU,GACX,EAAE,mBAAmB,CAAC,OAAO,CAAC,2CA+I9B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/register/index.tsx"],"names":[],"mappings":"AAiBA,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,4BAA4B,EAClC,MAAM,uCAAuC,CAAC;AAY/C,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGlD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gCAAgC;IAChC,aAAa,EAAE,OAAO,CAAC;IACvB,8CAA8C;IAC9C,qBAAqB,EAAE,OAAO,CAAC;IAC/B,kDAAkD;IAClD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,0EAA0E;IAC1E,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,mBAAmB,CAAC,OAAO,GAAG,OAAO,IAAI;IACnD,SAAS,EAAE,MAAM,GAAG,eAAe,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,eAAe,CAAC,EAAE,uBAAuB,CAAC;IAC1C,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC,qBAAqB,CAAC,EAAE,4BAA4B,CAAC;IACrD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACvC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,KAAK,CAAC,EAAE,iBAAiB,CAAC;CAC3B,CAAC;AAYF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,OAAO,EAAE,EAC/C,SAAS,EACT,SAAS,EACT,sBAAkC,EAClC,eAAe,EACf,MAAM,EACN,aAAa,EACb,qBAAqB,EACrB,eAAsB,EACtB,WAAW,EACX,sBAAoD,EACpD,gBAAuB,EACvB,oBAA4B,EAC5B,qBAAqC,EACrC,cAAoB,EACpB,UAA+B,EAC/B,WAAuB,EACvB,UAAU,EACV,KAAK,GACN,EAAE,mBAAmB,CAAC,OAAO,CAAC,2CAgN9B"}
@@ -4,6 +4,7 @@
4
4
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
5
5
  // section: imports
6
6
  import Link from "next/link";
7
+ import { useSearchParams } from "next/navigation";
7
8
  import { Input } from "../../ui/input.js";
8
9
  import { PasswordField } from "../shared/components/password_field.js";
9
10
  import { FormFieldWrapper } from "../shared/components/form_field_wrapper.js";
@@ -11,6 +12,9 @@ import { FormHeader } from "../shared/components/form_header.js";
11
12
  import { FormActionButtons } from "../shared/components/form_action_buttons.js";
12
13
  import { TwoColumnAuthLayout } from "../shared/components/two_column_auth_layout.js";
13
14
  import { AlreadyLoggedInGuard } from "../shared/components/already_logged_in_guard.js";
15
+ import { GoogleSignInButton } from "../shared/components/google_sign_in_button.js";
16
+ import { OAuthDivider } from "../shared/components/oauth_divider.js";
17
+ import { AlertCircle } from "lucide-react";
14
18
  import { REGISTER_FIELD_IDS, createRegisterFieldDefinitions, resolveRegisterButtonPalette, resolveRegisterLabels, resolveRegisterPasswordRequirements, } from "./config/register_field_config.js";
15
19
  import { use_register_form, } from "./hooks/use_register_form.js";
16
20
  const ORDERED_FIELDS = [
@@ -20,7 +24,29 @@ const ORDERED_FIELDS = [
20
24
  REGISTER_FIELD_IDS.CONFIRM_PASSWORD,
21
25
  ];
22
26
  // section: component
23
- export default function register_layout({ image_src, image_alt, image_background_color = "#f1f5f9", field_overrides, labels, button_colors, password_requirements, show_name_field = true, data_client, alreadyLoggedInMessage = "You are already logged in", showLogoutButton = true, showReturnHomeButton = false, returnHomeButtonLabel = "Return home", returnHomePath = "/", signInPath = "/hazo_auth/login", signInLabel = "Sign in", urlOnLogon, }) {
27
+ export default function register_layout({ image_src, image_alt, image_background_color = "#f1f5f9", field_overrides, labels, button_colors, password_requirements, show_name_field = true, data_client, alreadyLoggedInMessage = "You are already logged in", showLogoutButton = true, showReturnHomeButton = false, returnHomeButtonLabel = "Return home", returnHomePath = "/", signInPath = "/hazo_auth/login", signInLabel = "Sign in", urlOnLogon, oauth, }) {
28
+ // Default OAuth config: both enabled
29
+ const oauthConfig = oauth || {
30
+ enable_google: true,
31
+ enable_email_password: true,
32
+ google_button_text: "Sign up with Google",
33
+ oauth_divider_text: "or continue with email",
34
+ };
35
+ // Read OAuth error from URL query params (e.g., ?error=AccessDenied)
36
+ const searchParams = useSearchParams();
37
+ const oauthError = searchParams.get("error");
38
+ const getOAuthErrorMessage = (error) => {
39
+ switch (error) {
40
+ case "AccessDenied":
41
+ return "Access was denied. You may have cancelled the sign-in or your account is not authorized.";
42
+ case "OAuthSignin":
43
+ case "OAuthCallback":
44
+ case "OAuthCreateAccount":
45
+ return "Something went wrong with Google sign-in. Please try again.";
46
+ default:
47
+ return "An error occurred during sign-in. Please try again.";
48
+ }
49
+ };
24
50
  const fieldDefinitions = createRegisterFieldDefinitions(field_overrides);
25
51
  const resolvedLabels = resolveRegisterLabels(labels);
26
52
  const resolvedButtonPalette = resolveRegisterButtonPalette(button_colors);
@@ -54,5 +80,5 @@ export default function register_layout({ image_src, image_alt, image_background
54
80
  return (_jsx(FormFieldWrapper, { fieldId: fieldDefinition.id, label: fieldDefinition.label, input: inputElement, errorMessage: shouldShowError }, fieldId));
55
81
  });
56
82
  };
57
- return (_jsx(AlreadyLoggedInGuard, { image_src: image_src, image_alt: image_alt, image_background_color: image_background_color, message: alreadyLoggedInMessage, showLogoutButton: showLogoutButton, showReturnHomeButton: showReturnHomeButton, returnHomeButtonLabel: returnHomeButtonLabel, returnHomePath: returnHomePath, children: _jsx(TwoColumnAuthLayout, { imageSrc: image_src, imageAlt: image_alt, imageBackgroundColor: image_background_color, formContent: _jsxs(_Fragment, { children: [_jsx(FormHeader, { heading: resolvedLabels.heading, subHeading: resolvedLabels.subHeading }), _jsxs("form", { className: "cls_register_layout_form_fields flex flex-col gap-5", onSubmit: form.handleSubmit, "aria-label": "Registration form", children: [renderFields(form), _jsx(FormActionButtons, { submitLabel: resolvedLabels.submitButton, cancelLabel: resolvedLabels.cancelButton, buttonPalette: resolvedButtonPalette, isSubmitDisabled: form.isSubmitDisabled, onCancel: form.handleCancel, submitAriaLabel: "Submit registration form", cancelAriaLabel: "Cancel registration form" }), _jsxs("div", { className: "cls_register_layout_sign_in_link flex items-center justify-center gap-1 text-sm text-muted-foreground", children: [_jsx("span", { children: "Already have an account?" }), _jsx(Link, { href: signInPath, className: "cls_register_layout_sign_in_link_text text-primary underline-offset-4 hover:underline", "aria-label": "Go to sign in page", children: signInLabel })] }), form.isSubmitting && (_jsx("div", { className: "cls_register_submitting_indicator text-sm text-slate-600 text-center", children: "Registering..." }))] })] }) }) }));
83
+ return (_jsx(AlreadyLoggedInGuard, { image_src: image_src, image_alt: image_alt, image_background_color: image_background_color, message: alreadyLoggedInMessage, showLogoutButton: showLogoutButton, showReturnHomeButton: showReturnHomeButton, returnHomeButtonLabel: returnHomeButtonLabel, returnHomePath: returnHomePath, children: _jsx(TwoColumnAuthLayout, { imageSrc: image_src, imageAlt: image_alt, imageBackgroundColor: image_background_color, formContent: _jsxs(_Fragment, { children: [_jsx(FormHeader, { heading: resolvedLabels.heading, subHeading: resolvedLabels.subHeading }), oauthError && (_jsxs("div", { className: "cls_register_layout_oauth_error flex items-center gap-2 rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700", children: [_jsx(AlertCircle, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }), _jsx("span", { children: getOAuthErrorMessage(oauthError) })] })), oauthConfig.enable_google && (_jsx("div", { className: "cls_register_layout_oauth_section", children: _jsx(GoogleSignInButton, { label: oauthConfig.google_button_text }) })), oauthConfig.enable_google && oauthConfig.enable_email_password && (_jsx(OAuthDivider, { text: oauthConfig.oauth_divider_text })), oauthConfig.enable_email_password && (_jsxs("form", { className: "cls_register_layout_form_fields flex flex-col gap-5", onSubmit: form.handleSubmit, "aria-label": "Registration form", children: [renderFields(form), _jsx(FormActionButtons, { submitLabel: resolvedLabels.submitButton, cancelLabel: resolvedLabels.cancelButton, buttonPalette: resolvedButtonPalette, isSubmitDisabled: form.isSubmitDisabled, onCancel: form.handleCancel, submitAriaLabel: "Submit registration form", cancelAriaLabel: "Cancel registration form" }), _jsxs("div", { className: "cls_register_layout_sign_in_link flex items-center justify-center gap-1 text-sm text-muted-foreground", children: [_jsx("span", { children: "Already have an account?" }), _jsx(Link, { href: signInPath, className: "cls_register_layout_sign_in_link_text text-primary underline-offset-4 hover:underline", "aria-label": "Go to sign in page", children: signInLabel })] }), form.isSubmitting && (_jsx("div", { className: "cls_register_submitting_indicator text-sm text-slate-600 text-center", children: "Registering..." }))] })), !oauthConfig.enable_email_password && oauthConfig.enable_google && (_jsxs("div", { className: "cls_register_layout_sign_in_link mt-4 flex items-center justify-center gap-1 text-sm text-muted-foreground", children: [_jsx("span", { children: "Already have an account?" }), _jsx(Link, { href: signInPath, className: "cls_register_layout_sign_in_link_text text-primary underline-offset-4 hover:underline", "aria-label": "Go to sign in page", children: signInLabel })] }))] }) }) }));
58
84
  }
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
3
  declare const buttonVariants: (props?: ({
4
- variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
4
+ variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | null | undefined;
5
5
  size?: "default" | "sm" | "lg" | "icon" | null | undefined;
6
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
7
  export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
@@ -18,6 +18,13 @@ export type RegisterConfig = {
18
18
  imageSrc: string;
19
19
  imageAlt: string;
20
20
  imageBackgroundColor: string;
21
+ /** OAuth configuration */
22
+ oauth: {
23
+ enable_google: boolean;
24
+ enable_email_password: boolean;
25
+ google_button_text: string;
26
+ oauth_divider_text: string;
27
+ };
21
28
  };
22
29
  /**
23
30
  * Reads register layout configuration from hazo_auth_config.ini file
@@ -1 +1 @@
1
- {"version":3,"file":"register_config.server.d.ts","sourceRoot":"","sources":["../../src/lib/register_config.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAcrB,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,oBAAoB,EAAE;QACpB,cAAc,EAAE,MAAM,CAAC;QACvB,iBAAiB,EAAE,OAAO,CAAC;QAC3B,iBAAiB,EAAE,OAAO,CAAC;QAC3B,cAAc,EAAE,OAAO,CAAC;QACxB,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,sBAAsB,EAAE,MAAM,CAAC;IAC/B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;CAC9B,CAAC;AAGF;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,cAAc,CA8DpD"}
1
+ {"version":3,"file":"register_config.server.d.ts","sourceRoot":"","sources":["../../src/lib/register_config.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAerB,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,oBAAoB,EAAE;QACpB,cAAc,EAAE,MAAM,CAAC;QACvB,iBAAiB,EAAE,OAAO,CAAC;QAC3B,iBAAiB,EAAE,OAAO,CAAC;QAC3B,cAAc,EAAE,OAAO,CAAC;QACxB,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,sBAAsB,EAAE,MAAM,CAAC;IAC/B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,0BAA0B;IAC1B,KAAK,EAAE;QACL,aAAa,EAAE,OAAO,CAAC;QACvB,qBAAqB,EAAE,OAAO,CAAC;QAC/B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,kBAAkB,EAAE,MAAM,CAAC;KAC5B,CAAC;CACH,CAAC;AAGF;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,cAAc,CA8EpD"}
@@ -6,6 +6,7 @@ import { get_config_boolean, get_config_value, read_config_section } from "./con
6
6
  import { get_password_requirements_config } from "./password_requirements_config.server.js";
7
7
  import { get_already_logged_in_config } from "./already_logged_in_config.server.js";
8
8
  import { get_user_fields_config } from "./user_fields_config.server.js";
9
+ import { get_oauth_config } from "./oauth_config.server.js";
9
10
  // Default image path - consuming apps should either:
10
11
  // 1. Configure their own image_src in hazo_auth_config.ini
11
12
  // 2. Copy the default images from node_modules/hazo_auth/public/hazo_auth/images/ to their public folder
@@ -37,6 +38,10 @@ export function get_register_config() {
37
38
  const imageSrc = get_config_value("hazo_auth__register_layout", "image_src", DEFAULT_REGISTER_IMAGE_PATH);
38
39
  const imageAlt = get_config_value("hazo_auth__register_layout", "image_alt", "Modern building representing user registration");
39
40
  const imageBackgroundColor = get_config_value("hazo_auth__register_layout", "image_background_color", "#e2e8f0");
41
+ // Get OAuth configuration (shared with login)
42
+ const oauthConfig = get_oauth_config();
43
+ // For the register page, default button text to "Sign up with Google" unless overridden
44
+ const registerGoogleButtonText = get_config_value("hazo_auth__oauth", "google_button_text_register", "Sign up with Google");
40
45
  return {
41
46
  showNameField,
42
47
  passwordRequirements,
@@ -50,5 +55,11 @@ export function get_register_config() {
50
55
  imageSrc,
51
56
  imageAlt,
52
57
  imageBackgroundColor,
58
+ oauth: {
59
+ enable_google: oauthConfig.enable_google,
60
+ enable_email_password: oauthConfig.enable_email_password,
61
+ google_button_text: registerGoogleButtonText,
62
+ oauth_divider_text: oauthConfig.oauth_divider_text,
63
+ },
53
64
  };
54
65
  }
@@ -0,0 +1,20 @@
1
+ import { NextRequest } from "next/server";
2
+ /**
3
+ * Rewrites request.url to use the public-facing origin when behind a reverse proxy.
4
+ *
5
+ * Behind proxies like Cloudflare Tunnel, Next.js sets request.url to the internal
6
+ * address (e.g., https://localhost:3000). This causes NextResponse.redirect() to
7
+ * resolve Location headers against the internal origin instead of the public domain.
8
+ *
9
+ * Apply this at the TOP of any route handler that uses NextResponse.redirect().
10
+ *
11
+ * @param request - The incoming NextRequest
12
+ * @returns A new NextRequest with corrected URL, or the original if no proxy detected
13
+ */
14
+ export declare function rewrite_request_for_proxy(request: NextRequest): NextRequest;
15
+ /**
16
+ * Gets the public-facing origin for constructing redirect URLs.
17
+ * Use this when you need the origin string but don't need to rewrite the request.
18
+ */
19
+ export declare function get_public_origin(request: NextRequest): string;
20
+ //# sourceMappingURL=proxy_request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy_request.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/proxy_request.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAqC1C;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,CAqB3E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAG9D"}
@@ -0,0 +1,74 @@
1
+ // file_description: Shared utility for rewriting request.url behind reverse proxies (Cloudflare Tunnel, nginx, AWS ALB)
2
+ import { NextRequest } from "next/server";
3
+ /**
4
+ * Detects the public-facing origin from proxy headers.
5
+ * Returns null if not behind a proxy (origins match).
6
+ */
7
+ function detect_public_origin(request_url, headers) {
8
+ // Priority 1: NEXTAUTH_URL env var (explicitly configured)
9
+ const nextauth_url = process.env.NEXTAUTH_URL;
10
+ if (nextauth_url) {
11
+ try {
12
+ const public_origin = new URL(nextauth_url).origin;
13
+ if (public_origin !== request_url.origin) {
14
+ return public_origin;
15
+ }
16
+ }
17
+ catch (_a) {
18
+ // Invalid NEXTAUTH_URL, fall through
19
+ }
20
+ }
21
+ // Priority 2: x-forwarded-host header (set by Cloudflare Tunnel, nginx, etc.)
22
+ const forwarded_host = headers.get("x-forwarded-host");
23
+ if (forwarded_host && forwarded_host !== request_url.hostname) {
24
+ const proto = headers.get("x-forwarded-proto") || "https";
25
+ return `${proto}://${forwarded_host}`;
26
+ }
27
+ // Priority 3: host header (Cloudflare Tunnel also sets this)
28
+ const host_header = headers.get("host");
29
+ if (host_header && host_header !== request_url.host) {
30
+ const proto = headers.get("x-forwarded-proto") || "https";
31
+ return `${proto}://${host_header}`;
32
+ }
33
+ return null;
34
+ }
35
+ /**
36
+ * Rewrites request.url to use the public-facing origin when behind a reverse proxy.
37
+ *
38
+ * Behind proxies like Cloudflare Tunnel, Next.js sets request.url to the internal
39
+ * address (e.g., https://localhost:3000). This causes NextResponse.redirect() to
40
+ * resolve Location headers against the internal origin instead of the public domain.
41
+ *
42
+ * Apply this at the TOP of any route handler that uses NextResponse.redirect().
43
+ *
44
+ * @param request - The incoming NextRequest
45
+ * @returns A new NextRequest with corrected URL, or the original if no proxy detected
46
+ */
47
+ export function rewrite_request_for_proxy(request) {
48
+ try {
49
+ const request_url = new URL(request.url);
50
+ const public_origin = detect_public_origin(request_url, request.headers);
51
+ if (!public_origin) {
52
+ return request; // Not behind a proxy, no rewriting needed
53
+ }
54
+ // Construct corrected URL: public origin + original path + query
55
+ const corrected_url = `${public_origin}${request_url.pathname}${request_url.search}`;
56
+ // Create new NextRequest with corrected URL, preserving everything else
57
+ return new NextRequest(corrected_url, {
58
+ method: request.method,
59
+ headers: request.headers,
60
+ body: request.body,
61
+ });
62
+ }
63
+ catch (_a) {
64
+ return request;
65
+ }
66
+ }
67
+ /**
68
+ * Gets the public-facing origin for constructing redirect URLs.
69
+ * Use this when you need the origin string but don't need to rewrite the request.
70
+ */
71
+ export function get_public_origin(request) {
72
+ const request_url = new URL(request.url);
73
+ return detect_public_origin(request_url, request.headers) || request_url.origin;
74
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"nextauth.d.ts","sourceRoot":"","sources":["../../../src/server/routes/nextauth.ts"],"names":[],"mappings":"AAQA,KAAK,eAAe,GAAG;IACrB,MAAM,EAAE,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CACzC,CAAC;AA0DF,wBAAsB,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,gBAGnE;AAED,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,gBAGpE"}
1
+ {"version":3,"file":"nextauth.d.ts","sourceRoot":"","sources":["../../../src/server/routes/nextauth.ts"],"names":[],"mappings":"AAQA,KAAK,eAAe,GAAG;IACrB,MAAM,EAAE,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CACzC,CAAC;AAyGF,wBAAsB,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,gBAEnE;AAED,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,gBAEpE"}
@@ -4,60 +4,106 @@
4
4
  import NextAuthImport from "next-auth";
5
5
  const NextAuth = NextAuthImport.default || NextAuthImport;
6
6
  import { get_nextauth_config } from "../../lib/auth/nextauth_config.js";
7
- // section: handler
8
- // Get config lazily to ensure environment variables are available
9
- function getHandler() {
10
- const config = get_nextauth_config();
11
- return NextAuth(config);
12
- }
13
- // Create handler lazily
14
- let cachedHandler = null;
15
- function getOrCreateHandler() {
16
- if (!cachedHandler) {
17
- cachedHandler = getHandler();
7
+ // section: proxy_detection
8
+ /**
9
+ * Detects the public-facing origin from request headers.
10
+ * Cloudflare tunnel sends: x-forwarded-host, x-forwarded-proto, cf-visitor, host
11
+ */
12
+ function detect_proxy_origin(request) {
13
+ try {
14
+ const request_url = new URL(request.url);
15
+ const forwarded_host = request.headers.get("x-forwarded-host");
16
+ const host_header = request.headers.get("host");
17
+ const forwarded_proto = request.headers.get("x-forwarded-proto") || "https";
18
+ // Debug logging in development
19
+ if (process.env.NODE_ENV === "development") {
20
+ console.log("[NextAuth Proxy] request.url:", request.url);
21
+ console.log("[NextAuth Proxy] request_url.hostname:", request_url.hostname, "request_url.host:", request_url.host);
22
+ console.log("[NextAuth Proxy] x-forwarded-host:", forwarded_host);
23
+ console.log("[NextAuth Proxy] host header:", host_header);
24
+ console.log("[NextAuth Proxy] x-forwarded-proto:", forwarded_proto);
25
+ }
26
+ // Check x-forwarded-host first (set by Cloudflare tunnel, nginx, etc.)
27
+ if (forwarded_host && forwarded_host !== request_url.hostname) {
28
+ const origin = `${forwarded_proto}://${forwarded_host}`;
29
+ if (process.env.NODE_ENV === "development") {
30
+ console.log("[NextAuth Proxy] Detected proxy via x-forwarded-host:", origin);
31
+ }
32
+ return origin;
33
+ }
34
+ // Check host header (Cloudflare tunnel also sets this to the tunnel domain)
35
+ if (host_header && host_header !== request_url.host) {
36
+ const origin = `${forwarded_proto}://${host_header}`;
37
+ if (process.env.NODE_ENV === "development") {
38
+ console.log("[NextAuth Proxy] Detected proxy via host header:", origin);
39
+ }
40
+ return origin;
41
+ }
42
+ if (process.env.NODE_ENV === "development") {
43
+ console.log("[NextAuth Proxy] No proxy detected");
44
+ }
18
45
  }
19
- return cachedHandler;
46
+ catch (e) {
47
+ console.error("[NextAuth Proxy] Error:", e);
48
+ }
49
+ return null;
20
50
  }
21
- // section: url_rewriting
51
+ // section: handler
22
52
  /**
23
- * Rewrites request.url to use the public-facing origin when behind a reverse proxy.
24
- * NextAuth uses request.url to construct the OAuth redirect_uri parameter.
25
- * Without this, redirect_uri points to the internal address (e.g. https://localhost:3000),
26
- * which is unreachable from the user's browser.
53
+ * Handles NextAuth requests with proxy/tunnel support.
54
+ *
55
+ * When behind a proxy (Cloudflare tunnel, nginx, etc.), temporarily sets
56
+ * NEXTAUTH_URL to the proxy origin so next-auth uses the correct baseUrl
57
+ * for cookies, CSRF, and OAuth callback URLs.
58
+ *
59
+ * IMPORTANT: NEXTAUTH_URL must remain overridden for the entire duration of
60
+ * the handler execution, because next-auth reads it lazily during request
61
+ * processing (not just at handler creation time). We restore it after the
62
+ * handler completes.
27
63
  */
28
- function rewrite_request_for_proxy(request) {
29
- const nextauth_url = process.env.NEXTAUTH_URL;
30
- if (!nextauth_url) {
31
- return request;
64
+ async function handle_request(request, context) {
65
+ const proxy_origin = detect_proxy_origin(request);
66
+ const original_nextauth_url = process.env.NEXTAUTH_URL;
67
+ // If behind a proxy, override NEXTAUTH_URL for the entire request lifecycle
68
+ if (proxy_origin) {
69
+ let auth_path = "/api/auth";
70
+ if (original_nextauth_url) {
71
+ try {
72
+ const parsed = new URL(original_nextauth_url);
73
+ auth_path = parsed.pathname;
74
+ }
75
+ catch (_a) {
76
+ // ignore
77
+ }
78
+ }
79
+ process.env.NEXTAUTH_URL = `${proxy_origin}${auth_path}`;
32
80
  }
33
81
  try {
34
- const public_origin = nextauth_url.replace(/\/$/, "");
35
- const request_url = new URL(request.url);
36
- // If origins already match, no rewriting needed (direct dev mode)
37
- if (request_url.origin === public_origin) {
38
- return request;
39
- }
40
- // Construct the corrected URL: public origin + original path + query
41
- const corrected_url = `${public_origin}${request_url.pathname}${request_url.search}`;
42
- // Create a new Request with the corrected URL, preserving everything else
43
- return new Request(corrected_url, {
44
- method: request.method,
45
- headers: request.headers,
46
- body: request.body,
47
- // @ts-expect-error - duplex is required for streaming bodies in Node.js
48
- duplex: "half",
49
- });
82
+ const config = get_nextauth_config();
83
+ const handler = NextAuth(config);
84
+ // Next.js 16 passes params as a Promise, but next-auth v4 expects it resolved
85
+ const resolved_params = await context.params;
86
+ const resolved_context = { params: resolved_params };
87
+ // Await the handler response before restoring NEXTAUTH_URL
88
+ const response = await handler(request, resolved_context);
89
+ return response;
50
90
  }
51
- catch (_a) {
52
- return request;
91
+ finally {
92
+ // Restore original NEXTAUTH_URL after handler completes
93
+ if (proxy_origin) {
94
+ if (original_nextauth_url) {
95
+ process.env.NEXTAUTH_URL = original_nextauth_url;
96
+ }
97
+ else {
98
+ delete process.env.NEXTAUTH_URL;
99
+ }
100
+ }
53
101
  }
54
102
  }
55
103
  // section: exports
56
104
  export async function GET(request, context) {
57
- const handler = getOrCreateHandler();
58
- return handler(rewrite_request_for_proxy(request), context);
105
+ return handle_request(request, context);
59
106
  }
60
107
  export async function POST(request, context) {
61
- const handler = getOrCreateHandler();
62
- return handler(rewrite_request_for_proxy(request), context);
108
+ return handle_request(request, context);
63
109
  }
@@ -4,5 +4,5 @@ import { NextRequest, NextResponse } from "next/server";
4
4
  * The user creation/linking is done in NextAuth signIn callback
5
5
  * This route just sets the hazo_auth session cookies
6
6
  */
7
- export declare function GET(request: NextRequest): Promise<NextResponse<unknown>>;
7
+ export declare function GET(original_request: NextRequest): Promise<NextResponse<unknown>>;
8
8
  //# sourceMappingURL=oauth_google_callback.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"oauth_google_callback.d.ts","sourceRoot":"","sources":["../../../src/server/routes/oauth_google_callback.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAwBxD;;;;GAIG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW,kCAgJ7C"}
1
+ {"version":3,"file":"oauth_google_callback.d.ts","sourceRoot":"","sources":["../../../src/server/routes/oauth_google_callback.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAwBxD;;;;GAIG;AACH,wBAAsB,GAAG,CAAC,gBAAgB,EAAE,WAAW,kCA6JtD"}
@@ -10,18 +10,28 @@ import { get_cookie_name, get_cookie_options, BASE_COOKIE_NAMES } from "../../li
10
10
  import { get_hazo_connect_instance } from "../../lib/hazo_connect_instance.server.js";
11
11
  import { get_post_login_redirect } from "../../lib/services/post_verification_service.js";
12
12
  import { get_oauth_config } from "../../lib/oauth_config.server.js";
13
- import { create_redirect_url } from "../../lib/utils/get_origin_url.js";
13
+ import { rewrite_request_for_proxy } from "../../lib/utils/proxy_request.js";
14
14
  // section: api_handler
15
15
  /**
16
16
  * Handles the OAuth callback after Google sign-in
17
17
  * The user creation/linking is done in NextAuth signIn callback
18
18
  * This route just sets the hazo_auth session cookies
19
19
  */
20
- export async function GET(request) {
20
+ export async function GET(original_request) {
21
+ // Rewrite request.url to public origin when behind a reverse proxy.
22
+ // This ensures all NextResponse.redirect() calls produce correct Location headers.
23
+ const request = rewrite_request_for_proxy(original_request);
21
24
  const logger = create_app_logger();
25
+ // Detect if request came through HTTPS proxy (Cloudflare tunnel, etc.)
26
+ const is_secure = original_request.headers.get("x-forwarded-proto") === "https" ||
27
+ request.url.startsWith("https://");
22
28
  try {
23
29
  // Get the NextAuth token from the session
24
- const token = (await getToken({ req: request }));
30
+ // When behind HTTPS proxy, next-auth uses __Secure- cookie prefix
31
+ const token = (await getToken({
32
+ req: request,
33
+ secureCookie: is_secure,
34
+ }));
25
35
  logger.debug("google_callback_token_received", {
26
36
  filename: get_filename(),
27
37
  line_number: get_line_number(),
@@ -37,7 +47,7 @@ export async function GET(request) {
37
47
  note: "No NextAuth token found - user may not have completed Google sign-in",
38
48
  });
39
49
  // Redirect to login with error — use .toString() to ensure absolute URL
40
- const login_url = create_redirect_url("/hazo_auth/login", request.url);
50
+ const login_url = new URL("/hazo_auth/login", request.url);
41
51
  login_url.searchParams.set("error", "oauth_failed");
42
52
  return NextResponse.redirect(login_url.toString());
43
53
  }
@@ -50,7 +60,7 @@ export async function GET(request) {
50
60
  has_hazo_user_id: !!token.hazo_user_id,
51
61
  has_google_id: !!token.google_id,
52
62
  });
53
- const login_url = create_redirect_url("/hazo_auth/login", request.url);
63
+ const login_url = new URL("/hazo_auth/login", request.url);
54
64
  login_url.searchParams.set("error", "oauth_incomplete");
55
65
  return NextResponse.redirect(login_url.toString());
56
66
  }
@@ -94,12 +104,13 @@ export async function GET(request) {
94
104
  invitation_table_error,
95
105
  });
96
106
  // Create redirect response — use .toString() to ensure absolute public-facing URL
97
- const redirect_url = create_redirect_url(determined_redirect, request.url);
107
+ const redirect_url = new URL(determined_redirect, request.url);
98
108
  const response = NextResponse.redirect(redirect_url.toString());
99
109
  // Set authentication cookies (same as login route, with configurable prefix and domain)
110
+ // secure=true when in production OR when accessed via HTTPS proxy (Cloudflare tunnel, etc.)
100
111
  const base_cookie_options = {
101
112
  httpOnly: true,
102
- secure: process.env.NODE_ENV === "production",
113
+ secure: process.env.NODE_ENV === "production" || is_secure,
103
114
  sameSite: "lax",
104
115
  path: "/",
105
116
  maxAge: 60 * 60 * 24 * 30, // 30 days
@@ -134,7 +145,7 @@ export async function GET(request) {
134
145
  error_message,
135
146
  error_stack,
136
147
  });
137
- const login_url = create_redirect_url("/hazo_auth/login", request.url);
148
+ const login_url = new URL("/hazo_auth/login", request.url);
138
149
  login_url.searchParams.set("error", "oauth_error");
139
150
  return NextResponse.redirect(login_url.toString());
140
151
  }
@@ -1 +1 @@
1
- {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../src/server_pages/register.tsx"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAOrB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,eAAe,CAAC;IAErC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AAIF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,SAAS,EACT,SAAS,EACT,sBAAsB,GACvB,GAAE,iBAAsB,2CA4BxB;AAGD,OAAO,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../src/server_pages/register.tsx"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAOrB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,eAAe,CAAC;IAErC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AAIF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,SAAS,EACT,SAAS,EACT,sBAAsB,GACvB,GAAE,iBAAsB,2CAkCxB;AAGD,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -40,7 +40,12 @@ export default function RegisterPage({ image_src, image_alt, image_background_co
40
40
  const finalImageAlt = image_alt || config.imageAlt;
41
41
  const finalImageBackgroundColor = image_background_color || config.imageBackgroundColor;
42
42
  // Pass serializable config to client wrapper, wrapped in AuthPageShell for navbar support
43
- return (_jsx(AuthPageShell, { children: _jsx(RegisterClientWrapper, { image_src: finalImageSrc, image_alt: finalImageAlt, image_background_color: finalImageBackgroundColor, showNameField: config.showNameField, passwordRequirements: config.passwordRequirements, alreadyLoggedInMessage: config.alreadyLoggedInMessage, showLogoutButton: config.showLogoutButton, showReturnHomeButton: config.showReturnHomeButton, returnHomeButtonLabel: config.returnHomeButtonLabel, returnHomePath: config.returnHomePath, signInPath: config.signInPath, signInLabel: config.signInLabel }) }));
43
+ return (_jsx(AuthPageShell, { children: _jsx(RegisterClientWrapper, { image_src: finalImageSrc, image_alt: finalImageAlt, image_background_color: finalImageBackgroundColor, showNameField: config.showNameField, passwordRequirements: config.passwordRequirements, alreadyLoggedInMessage: config.alreadyLoggedInMessage, showLogoutButton: config.showLogoutButton, showReturnHomeButton: config.showReturnHomeButton, returnHomeButtonLabel: config.returnHomeButtonLabel, returnHomePath: config.returnHomePath, signInPath: config.signInPath, signInLabel: config.signInLabel, oauth: {
44
+ enable_google: config.oauth.enable_google,
45
+ enable_email_password: config.oauth.enable_email_password,
46
+ google_button_text: config.oauth.google_button_text,
47
+ oauth_divider_text: config.oauth.oauth_divider_text,
48
+ } }) }));
44
49
  }
45
50
  // Named export for direct imports
46
51
  export { RegisterPage };
@@ -1,13 +1,16 @@
1
1
  import type { RegisterConfig } from "../lib/register_config.server";
2
+ import type { OAuthLayoutConfig } from "../components/layouts/register/index";
2
3
  import type { StaticImageData } from "next/image";
3
- export type RegisterClientWrapperProps = Omit<RegisterConfig, 'imageSrc' | 'imageAlt' | 'imageBackgroundColor'> & {
4
+ export type RegisterClientWrapperProps = Omit<RegisterConfig, 'imageSrc' | 'imageAlt' | 'imageBackgroundColor' | 'oauth'> & {
4
5
  image_src: string | StaticImageData;
5
6
  image_alt: string;
6
7
  image_background_color: string;
8
+ /** OAuth configuration */
9
+ oauth?: OAuthLayoutConfig;
7
10
  };
8
11
  /**
9
12
  * Client wrapper for RegisterLayout
10
13
  * Initializes hazo_connect data client on client side and passes config from server
11
14
  */
12
- export declare function RegisterClientWrapper({ image_src, image_alt, image_background_color, showNameField, passwordRequirements, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, signInPath, signInLabel, }: RegisterClientWrapperProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function RegisterClientWrapper({ image_src, image_alt, image_background_color, showNameField, passwordRequirements, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, signInPath, signInLabel, oauth, }: RegisterClientWrapperProps): import("react/jsx-runtime").JSX.Element;
13
16
  //# sourceMappingURL=register_client_wrapper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"register_client_wrapper.d.ts","sourceRoot":"","sources":["../../src/server_pages/register_client_wrapper.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAGpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAAC,cAAc,EAAE,UAAU,GAAG,UAAU,GAAG,sBAAsB,CAAC,GAAG;IAChH,SAAS,EAAE,MAAM,GAAG,eAAe,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB,EAAE,MAAM,CAAC;CAChC,CAAC;AAGF;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,SAAS,EACT,sBAAsB,EACtB,aAAa,EACb,oBAAoB,EACpB,sBAAsB,EACtB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,EACd,UAAU,EACV,WAAW,GACZ,EAAE,0BAA0B,2CAqC5B"}
1
+ {"version":3,"file":"register_client_wrapper.d.ts","sourceRoot":"","sources":["../../src/server_pages/register_client_wrapper.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAG9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAAC,cAAc,EAAE,UAAU,GAAG,UAAU,GAAG,sBAAsB,GAAG,OAAO,CAAC,GAAG;IAC1H,SAAS,EAAE,MAAM,GAAG,eAAe,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,0BAA0B;IAC1B,KAAK,CAAC,EAAE,iBAAiB,CAAC;CAC3B,CAAC;AAGF;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,SAAS,EACT,sBAAsB,EACtB,aAAa,EACb,oBAAoB,EACpB,sBAAsB,EACtB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,EACd,UAAU,EACV,WAAW,EACX,KAAK,GACN,EAAE,0BAA0B,2CAsC5B"}
@@ -11,7 +11,7 @@ import { create_sqlite_hazo_connect } from "../lib/hazo_connect_setup.js";
11
11
  * Client wrapper for RegisterLayout
12
12
  * Initializes hazo_connect data client on client side and passes config from server
13
13
  */
14
- export function RegisterClientWrapper({ image_src, image_alt, image_background_color, showNameField, passwordRequirements, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, signInPath, signInLabel, }) {
14
+ export function RegisterClientWrapper({ image_src, image_alt, image_background_color, showNameField, passwordRequirements, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, signInPath, signInLabel, oauth, }) {
15
15
  const [dataClient, setDataClient] = useState(null);
16
16
  useEffect(() => {
17
17
  // Initialize hazo_connect on client side
@@ -23,5 +23,5 @@ export function RegisterClientWrapper({ image_src, image_alt, image_background_c
23
23
  if (!dataClient) {
24
24
  return (_jsx("div", { className: "cls_register_page_loading flex items-center justify-center min-h-screen", children: _jsx("div", { className: "text-slate-600 animate-pulse", children: "Loading..." }) }));
25
25
  }
26
- return (_jsx(RegisterLayout, { image_src: image_src, image_alt: image_alt, image_background_color: image_background_color, data_client: dataClient, show_name_field: showNameField, password_requirements: passwordRequirements, alreadyLoggedInMessage: alreadyLoggedInMessage, showLogoutButton: showLogoutButton, showReturnHomeButton: showReturnHomeButton, returnHomeButtonLabel: returnHomeButtonLabel, returnHomePath: returnHomePath, signInPath: signInPath, signInLabel: signInLabel }));
26
+ return (_jsx(RegisterLayout, { image_src: image_src, image_alt: image_alt, image_background_color: image_background_color, data_client: dataClient, show_name_field: showNameField, password_requirements: passwordRequirements, alreadyLoggedInMessage: alreadyLoggedInMessage, showLogoutButton: showLogoutButton, showReturnHomeButton: showReturnHomeButton, returnHomeButtonLabel: returnHomeButtonLabel, returnHomePath: returnHomePath, signInPath: signInPath, signInLabel: signInLabel, oauth: oauth }));
27
27
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hazo_auth",
3
- "version": "5.1.33",
3
+ "version": "5.1.35",
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",
@@ -188,7 +188,7 @@
188
188
  "scripts": {
189
189
  "dev": "next dev",
190
190
  "build": "next build",
191
- "build:pkg": "tsc -p tsconfig.build.json && tsx scripts/copy_assets.ts",
191
+ "build:pkg": "tsc --jsx react-jsx --skipLibCheck -p tsconfig.build.json && tsx scripts/copy_assets.ts",
192
192
  "prepublishOnly": "npm run build:pkg",
193
193
  "validate": "tsx scripts/validate_setup.ts",
194
194
  "generate-routes": "tsx scripts/generate_routes.ts",
@@ -1,32 +0,0 @@
1
- export * from "./auth/index.js";
2
- export * from "./services/index.js";
3
- export { cn, merge_class_names } from "./utils.js";
4
- export { get_config_value, get_config_number, get_config_boolean, get_config_array, read_config_section } from "./config/config_loader.server.js";
5
- export { create_sqlite_hazo_connect } from "./hazo_connect_setup.js";
6
- export { get_hazo_connect_instance } from "./hazo_connect_instance.server.js";
7
- export { create_app_logger } from "./app_logger.js";
8
- export { get_login_config } from "./login_config.server.js";
9
- export { get_register_config } from "./register_config.server.js";
10
- export { get_forgot_password_config } from "./forgot_password_config.server.js";
11
- export { get_reset_password_config } from "./reset_password_config.server.js";
12
- export { get_email_verification_config } from "./email_verification_config.server.js";
13
- export { get_my_settings_config } from "./my_settings_config.server.js";
14
- export { get_user_management_config } from "./user_management_config.server.js";
15
- export { get_profile_picture_config } from "./profile_picture_config.server.js";
16
- export { get_profile_pic_menu_config } from "./profile_pic_menu_config.server.js";
17
- export { get_already_logged_in_config } from "./already_logged_in_config.server.js";
18
- export { get_ui_shell_config } from "./ui_shell_config.server.js";
19
- export { get_ui_sizes_config } from "./ui_sizes_config.server.js";
20
- export { get_auth_utility_config } from "./auth_utility_config.server.js";
21
- export { get_password_requirements_config } from "./password_requirements_config.server.js";
22
- export { get_messages_config } from "./messages_config.server.js";
23
- export { get_user_fields_config } from "./user_fields_config.server.js";
24
- export { get_file_types_config } from "./file_types_config.server.js";
25
- export { get_oauth_config, is_google_oauth_enabled, is_email_password_enabled } from "./oauth_config.server.js";
26
- export type { OAuthConfig } from "./oauth_config.server";
27
- export { get_branding_config, is_branding_enabled, is_allowed_logo_format, get_max_logo_size_bytes } from "./branding_config.server.js";
28
- export type { FirmBrandingConfig } from "./branding_config.server";
29
- export { sanitize_error_for_user } from "./utils/error_sanitizer.js";
30
- export type { ErrorSanitizationOptions } from "./utils/error_sanitizer";
31
- export * from "./utils/api_route_helpers.js";
32
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAEA,cAAc,cAAc,CAAC;AAG7B,cAAc,kBAAkB,CAAC;AAGjC,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAGhD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAG/I,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAG3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AACnF,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,gCAAgC,EAAE,MAAM,uCAAuC,CAAC;AACzF,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAC7G,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACrI,YAAY,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAGnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,YAAY,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,cAAc,2BAA2B,CAAC"}
package/dist/lib/index.js DELETED
@@ -1,37 +0,0 @@
1
- // file_description: barrel export for lib utilities
2
- // section: auth_exports
3
- export * from "./auth/index.js";
4
- // section: service_exports
5
- export * from "./services/index.js";
6
- // section: utility_exports
7
- export { cn, merge_class_names } from "./utils.js";
8
- // section: config_exports
9
- export { get_config_value, get_config_number, get_config_boolean, get_config_array, read_config_section } from "./config/config_loader.server.js";
10
- // section: hazo_connect_exports
11
- export { create_sqlite_hazo_connect } from "./hazo_connect_setup.js";
12
- export { get_hazo_connect_instance } from "./hazo_connect_instance.server.js";
13
- // section: logger_exports
14
- export { create_app_logger } from "./app_logger.js";
15
- // section: config_server_exports
16
- export { get_login_config } from "./login_config.server.js";
17
- export { get_register_config } from "./register_config.server.js";
18
- export { get_forgot_password_config } from "./forgot_password_config.server.js";
19
- export { get_reset_password_config } from "./reset_password_config.server.js";
20
- export { get_email_verification_config } from "./email_verification_config.server.js";
21
- export { get_my_settings_config } from "./my_settings_config.server.js";
22
- export { get_user_management_config } from "./user_management_config.server.js";
23
- export { get_profile_picture_config } from "./profile_picture_config.server.js";
24
- export { get_profile_pic_menu_config } from "./profile_pic_menu_config.server.js";
25
- export { get_already_logged_in_config } from "./already_logged_in_config.server.js";
26
- export { get_ui_shell_config } from "./ui_shell_config.server.js";
27
- export { get_ui_sizes_config } from "./ui_sizes_config.server.js";
28
- export { get_auth_utility_config } from "./auth_utility_config.server.js";
29
- export { get_password_requirements_config } from "./password_requirements_config.server.js";
30
- export { get_messages_config } from "./messages_config.server.js";
31
- export { get_user_fields_config } from "./user_fields_config.server.js";
32
- export { get_file_types_config } from "./file_types_config.server.js";
33
- export { get_oauth_config, is_google_oauth_enabled, is_email_password_enabled } from "./oauth_config.server.js";
34
- export { get_branding_config, is_branding_enabled, is_allowed_logo_format, get_max_logo_size_bytes } from "./branding_config.server.js";
35
- // section: util_exports
36
- export { sanitize_error_for_user } from "./utils/error_sanitizer.js";
37
- export * from "./utils/api_route_helpers.js";