hazo_auth 5.1.40 → 5.2.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.
Files changed (55) hide show
  1. package/cli-src/lib/auth/ensure_anon_id.server.ts +88 -0
  2. package/cli-src/lib/auth/index.ts +3 -0
  3. package/cli-src/lib/cookies_config.edge.ts +1 -0
  4. package/cli-src/lib/cookies_config.server.ts +1 -0
  5. package/cli-src/lib/hazo_connect_setup.server.ts +0 -8
  6. package/cli-src/lib/services/session_token_service.ts +2 -2
  7. package/cli-src/lib/ui_shell_config.server.ts +6 -2
  8. package/dist/components/layouts/login/index.d.ts +16 -3
  9. package/dist/components/layouts/login/index.d.ts.map +1 -1
  10. package/dist/components/layouts/login/index.js +49 -5
  11. package/dist/components/layouts/register/index.d.ts +7 -3
  12. package/dist/components/layouts/register/index.d.ts.map +1 -1
  13. package/dist/components/layouts/register/index.js +36 -3
  14. package/dist/components/layouts/shared/components/floating_home_link.d.ts +20 -0
  15. package/dist/components/layouts/shared/components/floating_home_link.d.ts.map +1 -0
  16. package/dist/components/layouts/shared/components/floating_home_link.js +29 -0
  17. package/dist/components/layouts/shared/components/profile_pic_menu.d.ts +8 -0
  18. package/dist/components/layouts/shared/components/profile_pic_menu.d.ts.map +1 -1
  19. package/dist/components/layouts/shared/components/profile_pic_menu.js +18 -8
  20. package/dist/components/layouts/shared/index.d.ts +2 -0
  21. package/dist/components/layouts/shared/index.d.ts.map +1 -1
  22. package/dist/components/layouts/shared/index.js +1 -0
  23. package/dist/components/ui/button.d.ts +2 -2
  24. package/dist/lib/auth/ensure_anon_id.server.d.ts +21 -0
  25. package/dist/lib/auth/ensure_anon_id.server.d.ts.map +1 -0
  26. package/dist/lib/auth/ensure_anon_id.server.js +73 -0
  27. package/dist/lib/auth/index.d.ts +1 -0
  28. package/dist/lib/auth/index.d.ts.map +1 -1
  29. package/dist/lib/auth/index.js +2 -0
  30. package/dist/lib/cookies_config.edge.d.ts +1 -0
  31. package/dist/lib/cookies_config.edge.d.ts.map +1 -1
  32. package/dist/lib/cookies_config.edge.js +1 -0
  33. package/dist/lib/cookies_config.server.d.ts +1 -0
  34. package/dist/lib/cookies_config.server.d.ts.map +1 -1
  35. package/dist/lib/cookies_config.server.js +1 -0
  36. package/dist/lib/hazo_connect_setup.server.d.ts.map +1 -1
  37. package/dist/lib/hazo_connect_setup.server.js +0 -8
  38. package/dist/lib/services/session_token_service.js +2 -2
  39. package/dist/lib/ui_shell_config.server.d.ts.map +1 -1
  40. package/dist/lib/ui_shell_config.server.js +6 -2
  41. package/dist/server/routes/oauth_google_callback.d.ts.map +1 -1
  42. package/dist/server/routes/oauth_google_callback.js +33 -6
  43. package/dist/server_pages/login.d.ts +13 -1
  44. package/dist/server_pages/login.d.ts.map +1 -1
  45. package/dist/server_pages/login.js +25 -9
  46. package/dist/server_pages/login_client_wrapper.d.ts +6 -4
  47. package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
  48. package/dist/server_pages/login_client_wrapper.js +2 -2
  49. package/dist/server_pages/register.d.ts +7 -1
  50. package/dist/server_pages/register.d.ts.map +1 -1
  51. package/dist/server_pages/register.js +18 -9
  52. package/dist/server_pages/register_client_wrapper.d.ts +6 -4
  53. package/dist/server_pages/register_client_wrapper.d.ts.map +1 -1
  54. package/dist/server_pages/register_client_wrapper.js +2 -2
  55. package/package.json +25 -20
@@ -0,0 +1,21 @@
1
+ import "server-only";
2
+ import { NextRequest } from "next/server";
3
+ /**
4
+ * Returns a stable opaque anonymous visitor ID for the caller, reading an
5
+ * existing httpOnly cookie or issuing a fresh UUID v4 if absent.
6
+ *
7
+ * - Cookie name: `hazo_auth_anon_id` (prefixed via `[hazo_auth__cookies] cookie_prefix`)
8
+ * - Cookie attributes: httpOnly, sameSite=lax, path=/, secure in production,
9
+ * maxAge = 2 years.
10
+ * - Idempotent per-request: calling twice with the same NextRequest returns
11
+ * the same id and only queues one Set-Cookie.
12
+ *
13
+ * Independent from authenticated identity: an authenticated user can still
14
+ * have an anon id cookie. Whether to consult it for a logged-in caller is up
15
+ * to the caller (hazo_feedback, for example, doesn't bother).
16
+ *
17
+ * @param request - The incoming NextRequest.
18
+ * @returns The existing or freshly-issued anonymous visitor id (UUID v4 string).
19
+ */
20
+ export declare function ensure_anon_id(request: NextRequest): Promise<string>;
21
+ //# sourceMappingURL=ensure_anon_id.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensure_anon_id.server.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/ensure_anon_id.server.ts"],"names":[],"mappings":"AAqBA,OAAO,aAAa,CAAC;AAGrB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAgB1C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B1E"}
@@ -0,0 +1,73 @@
1
+ // file_description: server-only helper to issue/read a stable opaque anonymous visitor ID via httpOnly cookie
2
+ //
3
+ // Used by hazo_feedback (and any future caller) to group submissions from the
4
+ // same anonymous visitor across page loads / weeks without requiring login.
5
+ //
6
+ // Signature choice (v5.2.0): async, request-only — Option A in the design doc.
7
+ // - Reads the existing cookie from the incoming `request.cookies`.
8
+ // - Writes a freshly-issued cookie via the async `cookies()` API from
9
+ // `next/headers`, which is the supported write surface in Server Components,
10
+ // Server Actions, and Route Handlers in Next.js 15+.
11
+ // - Tradeoff: NOT valid in Edge middleware (where `next/headers` throws). If a
12
+ // middleware caller ever needs this, add a sibling `ensure_anon_id_edge`
13
+ // that takes (request, response) and writes via `response.cookies.set`.
14
+ //
15
+ // Idempotent per-request: a per-request WeakMap remembers which id was issued
16
+ // for a given NextRequest instance, so calling `ensure_anon_id(request)` twice
17
+ // in the same request returns the same id and queues only one Set-Cookie. This
18
+ // matters because `request.cookies` reflects the *incoming* state and won't
19
+ // see an id that the same request just issued.
20
+ // section: server-only-guard
21
+ import "server-only";
22
+ import { cookies } from "next/headers";
23
+ import { BASE_COOKIE_NAMES, get_cookie_name, get_cookie_options, } from "../cookies_config.server.js";
24
+ // section: constants
25
+ const TWO_YEARS_SECONDS = 60 * 60 * 24 * 365 * 2;
26
+ // section: state
27
+ // WeakMap keyed by NextRequest so entries are GC'd with the request.
28
+ const issued_per_request = new WeakMap();
29
+ // section: main_function
30
+ /**
31
+ * Returns a stable opaque anonymous visitor ID for the caller, reading an
32
+ * existing httpOnly cookie or issuing a fresh UUID v4 if absent.
33
+ *
34
+ * - Cookie name: `hazo_auth_anon_id` (prefixed via `[hazo_auth__cookies] cookie_prefix`)
35
+ * - Cookie attributes: httpOnly, sameSite=lax, path=/, secure in production,
36
+ * maxAge = 2 years.
37
+ * - Idempotent per-request: calling twice with the same NextRequest returns
38
+ * the same id and only queues one Set-Cookie.
39
+ *
40
+ * Independent from authenticated identity: an authenticated user can still
41
+ * have an anon id cookie. Whether to consult it for a logged-in caller is up
42
+ * to the caller (hazo_feedback, for example, doesn't bother).
43
+ *
44
+ * @param request - The incoming NextRequest.
45
+ * @returns The existing or freshly-issued anonymous visitor id (UUID v4 string).
46
+ */
47
+ export async function ensure_anon_id(request) {
48
+ var _a;
49
+ const cookie_name = get_cookie_name(BASE_COOKIE_NAMES.ANON_ID);
50
+ // Per-request idempotency: if we already issued for this NextRequest, reuse it.
51
+ const already_issued = issued_per_request.get(request);
52
+ if (already_issued) {
53
+ return already_issued;
54
+ }
55
+ // Read the existing cookie from the incoming request.
56
+ const existing = (_a = request.cookies.get(cookie_name)) === null || _a === void 0 ? void 0 : _a.value;
57
+ if (existing) {
58
+ return existing;
59
+ }
60
+ // Issue a new id and queue the Set-Cookie via the next/headers cookie store.
61
+ const new_id = crypto.randomUUID();
62
+ const cookie_options = get_cookie_options({
63
+ httpOnly: true,
64
+ secure: process.env.NODE_ENV === "production",
65
+ sameSite: "lax",
66
+ path: "/",
67
+ maxAge: TWO_YEARS_SECONDS,
68
+ });
69
+ const cookie_store = await cookies();
70
+ cookie_store.set(cookie_name, new_id, cookie_options);
71
+ issued_per_request.set(request, new_id);
72
+ return new_id;
73
+ }
@@ -2,6 +2,7 @@ export * from "./auth_types.js";
2
2
  export { hazo_get_auth } from "./hazo_get_auth.server.js";
3
3
  export { get_authenticated_user, require_auth, is_authenticated, } from "./auth_utils.server.js";
4
4
  export type { AuthResult, AuthUser } from "./auth_utils.server";
5
+ export { ensure_anon_id } from "./ensure_anon_id.server.js";
5
6
  export { hazo_get_tenant_auth, require_tenant_auth, extract_scope_id_from_request, } from "./hazo_get_tenant_auth.server.js";
6
7
  export type { ScopeDetails, TenantOrganization, TenantAuthOptions, TenantAuthResult, RequiredTenantAuthResult, } from "./auth_types";
7
8
  export { HazoAuthError, AuthenticationRequiredError, TenantRequiredError, TenantAccessDeniedError, } from "./auth_types.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/index.ts"],"names":[],"mappings":"AAEA,cAAc,cAAc,CAAC;AAG7B,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGhE,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,6BAA6B,GAC9B,MAAM,+BAA+B,CAAC;AACvC,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,aAAa,EACb,2BAA2B,EAC3B,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EACL,QAAQ,EACR,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,uBAAuB,EACvB,8BAA8B,EAC9B,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/index.ts"],"names":[],"mappings":"AAEA,cAAc,cAAc,CAAC;AAG7B,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGhE,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAGzD,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,6BAA6B,GAC9B,MAAM,+BAA+B,CAAC;AACvC,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,aAAa,EACb,2BAA2B,EAC3B,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EACL,QAAQ,EACR,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,uBAAuB,EACvB,8BAA8B,EAC9B,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -4,6 +4,8 @@ export * from "./auth_types.js";
4
4
  // section: server_exports
5
5
  export { hazo_get_auth } from "./hazo_get_auth.server.js";
6
6
  export { get_authenticated_user, require_auth, is_authenticated, } from "./auth_utils.server.js";
7
+ // section: anon_id_exports (v5.2)
8
+ export { ensure_anon_id } from "./ensure_anon_id.server.js";
7
9
  // section: tenant_auth_exports
8
10
  export { hazo_get_tenant_auth, require_tenant_auth, extract_scope_id_from_request, } from "./hazo_get_tenant_auth.server.js";
9
11
  export { HazoAuthError, AuthenticationRequiredError, TenantRequiredError, TenantAccessDeniedError, } from "./auth_types.js";
@@ -3,6 +3,7 @@ export declare const BASE_COOKIE_NAMES: {
3
3
  readonly USER_EMAIL: "hazo_auth_user_email";
4
4
  readonly SESSION: "hazo_auth_session";
5
5
  readonly DEV_LOCK: "hazo_auth_dev_lock";
6
+ readonly ANON_ID: "hazo_auth_anon_id";
6
7
  };
7
8
  /**
8
9
  * Gets the cookie prefix from environment variable
@@ -1 +1 @@
1
- {"version":3,"file":"cookies_config.edge.d.ts","sourceRoot":"","sources":["../../src/lib/cookies_config.edge.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,iBAAiB;;;;;CAKpB,CAAC;AAGX;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAc/C;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAQ9G"}
1
+ {"version":3,"file":"cookies_config.edge.d.ts","sourceRoot":"","sources":["../../src/lib/cookies_config.edge.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,iBAAiB;;;;;;CAMpB,CAAC;AAGX;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAc/C;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAQ9G"}
@@ -7,6 +7,7 @@ export const BASE_COOKIE_NAMES = {
7
7
  USER_EMAIL: "hazo_auth_user_email",
8
8
  SESSION: "hazo_auth_session",
9
9
  DEV_LOCK: "hazo_auth_dev_lock",
10
+ ANON_ID: "hazo_auth_anon_id", // v5.2: Stable opaque per-visitor ID for anonymous flows (e.g. hazo_feedback). Kept in sync with the server constant.
10
11
  };
11
12
  // section: main_functions
12
13
  /**
@@ -11,6 +11,7 @@ export declare const BASE_COOKIE_NAMES: {
11
11
  readonly SESSION: "hazo_auth_session";
12
12
  readonly DEV_LOCK: "hazo_auth_dev_lock";
13
13
  readonly SCOPE_ID: "hazo_auth_scope_id";
14
+ readonly ANON_ID: "hazo_auth_anon_id";
14
15
  };
15
16
  /**
16
17
  * Reads cookie configuration from hazo_auth_config.ini file
@@ -1 +1 @@
1
- {"version":3,"file":"cookies_config.server.d.ts","sourceRoot":"","sources":["../../src/lib/cookies_config.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAMrB,MAAM,MAAM,aAAa,GAAG;IAC1B,6FAA6F;IAC7F,aAAa,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAYF,eAAO,MAAM,iBAAiB;;;;;;CAMpB,CAAC;AAGX;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,aAAa,CAoBlD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAQzG;AAKD;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,aAAa,CAKzD;AAED;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD"}
1
+ {"version":3,"file":"cookies_config.server.d.ts","sourceRoot":"","sources":["../../src/lib/cookies_config.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAMrB,MAAM,MAAM,aAAa,GAAG;IAC1B,6FAA6F;IAC7F,aAAa,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAYF,eAAO,MAAM,iBAAiB;;;;;;;CAOpB,CAAC;AAGX;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,aAAa,CAoBlD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAQzG;AAKD;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,aAAa,CAKzD;AAED;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD"}
@@ -16,6 +16,7 @@ export const BASE_COOKIE_NAMES = {
16
16
  SESSION: "hazo_auth_session",
17
17
  DEV_LOCK: "hazo_auth_dev_lock",
18
18
  SCOPE_ID: "hazo_auth_scope_id", // v5.2: Tenant context cookie for multi-tenancy
19
+ ANON_ID: "hazo_auth_anon_id", // v5.2: Stable opaque per-visitor ID for anonymous flows (e.g. hazo_feedback)
19
20
  };
20
21
  // section: main_function
21
22
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"hazo_connect_setup.server.d.ts","sourceRoot":"","sources":["../../src/lib/hazo_connect_setup.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AA8JrB;;;;GAIG;AACH,wBAAgB,iCAAiC,8CAuBhD;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,IAAI;IACjD,IAAI,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAsBA"}
1
+ {"version":3,"file":"hazo_connect_setup.server.d.ts","sourceRoot":"","sources":["../../src/lib/hazo_connect_setup.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAsJrB;;;;GAIG;AACH,wBAAgB,iCAAiC,8CAuBhD;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,IAAI;IACjD,IAAI,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAsBA"}
@@ -78,14 +78,6 @@ function get_hazo_connect_config() {
78
78
  }
79
79
  }
80
80
  }
81
- // Log the resolved path so consumers can see which DB file is being used
82
- logger.info("hazo_connect_sqlite_path_resolved", {
83
- filename: "hazo_connect_setup.server.ts",
84
- line_number: 0,
85
- sqlite_path,
86
- process_cwd: process.cwd(),
87
- config_source: hazo_connect_section ? "hazo_auth_config.ini" : "environment variables",
88
- });
89
81
  return {
90
82
  type: "sqlite",
91
83
  sqlitePath: sqlite_path,
@@ -59,7 +59,7 @@ export async function create_session_token(user_id, email, managed_by_user_id) {
59
59
  .setIssuedAt(now)
60
60
  .setExpirationTime(exp)
61
61
  .sign(secret);
62
- logger.info("session_token_created", {
62
+ logger.debug("session_token_created", {
63
63
  filename: get_filename(),
64
64
  line_number: get_line_number(),
65
65
  user_id,
@@ -107,7 +107,7 @@ export async function validate_session_token(token) {
107
107
  });
108
108
  return { valid: false };
109
109
  }
110
- logger.info("session_token_validated", {
110
+ logger.debug("session_token_validated", {
111
111
  filename: get_filename(),
112
112
  line_number: get_line_number(),
113
113
  user_id,
@@ -1 +1 @@
1
- {"version":3,"file":"ui_shell_config.server.d.ts","sourceRoot":"","sources":["../../src/lib/ui_shell_config.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAIrB,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAG9E,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG,YAAY,CAAC;AAE9D,MAAM,MAAM,aAAa,GAAG;IAC1B,WAAW,EAAE,iBAAiB,CAAC;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,wBAAwB,EAAE,MAAM,CAAC;IACjC,wBAAwB,EAAE,MAAM,CAAC;IACjC,uBAAuB,EAAE,OAAO,CAAC;IACjC,2BAA2B,EAAE,OAAO,CAAC;IACrC,+CAA+C;IAC/C,MAAM,EAAE,YAAY,CAAC;IACrB,mDAAmD;IACnD,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAGF;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,aAAa,CAyDnD"}
1
+ {"version":3,"file":"ui_shell_config.server.d.ts","sourceRoot":"","sources":["../../src/lib/ui_shell_config.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAIrB,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAG9E,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG,YAAY,CAAC;AAE9D,MAAM,MAAM,aAAa,GAAG;IAC1B,WAAW,EAAE,iBAAiB,CAAC;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,wBAAwB,EAAE,MAAM,CAAC;IACjC,wBAAwB,EAAE,MAAM,CAAC;IACjC,uBAAuB,EAAE,OAAO,CAAC;IACjC,2BAA2B,EAAE,OAAO,CAAC;IACrC,+CAA+C;IAC/C,MAAM,EAAE,YAAY,CAAC;IACrB,mDAAmD;IACnD,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAGF;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,aAAa,CA6DnD"}
@@ -17,8 +17,12 @@ export function get_ui_shell_config() {
17
17
  const standalone_description = get_config_value(section, "standalone_description", "Reuse the packaged authentication flows while inheriting your existing app shell styles.");
18
18
  const standalone_wrapper_class = get_config_value(section, "standalone_wrapper_class", "cls_standalone_shell flex min-h-screen w-full items-center justify-center bg-background px-4 py-10");
19
19
  const standalone_content_class = get_config_value(section, "standalone_content_class", "cls_standalone_shell_content w-full max-w-5xl shadow-xl rounded-2xl border bg-card");
20
- const standalone_show_heading = get_config_value(section, "standalone_show_heading", "true").toLowerCase() === "true";
21
- const standalone_show_description = get_config_value(section, "standalone_show_description", "true").toLowerCase() === "true";
20
+ // Default to false: the package-default copy ("Welcome to hazo auth" /
21
+ // "Reuse the packaged authentication flows...") is dev/test placeholder
22
+ // text, not production-suitable. Consumers who want their own heading set
23
+ // standalone_heading + standalone_show_heading=true explicitly.
24
+ const standalone_show_heading = get_config_value(section, "standalone_show_heading", "false").toLowerCase() === "true";
25
+ const standalone_show_description = get_config_value(section, "standalone_show_description", "false").toLowerCase() === "true";
22
26
  const vertical_center = get_config_value(section, "vertical_center", "true").toLowerCase() === "true";
23
27
  const navbar = get_navbar_config();
24
28
  return {
@@ -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,gBAAgB,EAAE,WAAW,kCA6JtD"}
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,kCA2LtD"}
@@ -25,10 +25,18 @@ export async function GET(original_request) {
25
25
  // Detect if request came through HTTPS proxy (Cloudflare tunnel, etc.)
26
26
  const is_secure = original_request.headers.get("x-forwarded-proto") === "https" ||
27
27
  request.url.startsWith("https://");
28
+ // Resolve the configured sign-in page up-front so the early error redirects
29
+ // (no token / missing data) honour [hazo_auth__oauth] sign_in_page just like
30
+ // the success path. Defaults to `/hazo_auth/login` for back-compat.
31
+ const sign_in_page = get_oauth_config().sign_in_page;
28
32
  try {
29
33
  // Get the NextAuth token from the session
30
34
  // When behind HTTPS proxy, next-auth uses __Secure- cookie prefix
31
35
  const token = (await getToken({
36
+ // next-auth@4 was typed against an older next/server NextRequest that
37
+ // still carried `geo`, `ip`, and the `[INTERNALS]` symbol. Next 16
38
+ // dropped those, leaving the two NextRequest types structurally
39
+ // incompatible at the type level even though the runtime shape is fine.
32
40
  req: request,
33
41
  secureCookie: is_secure,
34
42
  }));
@@ -47,7 +55,7 @@ export async function GET(original_request) {
47
55
  note: "No NextAuth token found - user may not have completed Google sign-in",
48
56
  });
49
57
  // Redirect to login with error — use .toString() to ensure absolute URL
50
- const login_url = new URL("/hazo_auth/login", request.url);
58
+ const login_url = new URL(sign_in_page, request.url);
51
59
  login_url.searchParams.set("error", "oauth_failed");
52
60
  return NextResponse.redirect(login_url.toString());
53
61
  }
@@ -60,13 +68,13 @@ export async function GET(original_request) {
60
68
  has_hazo_user_id: !!token.hazo_user_id,
61
69
  has_google_id: !!token.google_id,
62
70
  });
63
- const login_url = new URL("/hazo_auth/login", request.url);
71
+ const login_url = new URL(sign_in_page, request.url);
64
72
  login_url.searchParams.set("error", "oauth_incomplete");
65
73
  return NextResponse.redirect(login_url.toString());
66
74
  }
67
75
  const user_id = token.hazo_user_id;
68
76
  const email = token.email;
69
- logger.info("google_callback_success", {
77
+ logger.debug("google_callback_success", {
70
78
  filename: get_filename(),
71
79
  line_number: get_line_number(),
72
80
  user_id,
@@ -75,13 +83,32 @@ export async function GET(original_request) {
75
83
  // Get redirect URL based on user's scope/invitation status
76
84
  const loginConfig = get_login_config();
77
85
  const oauthConfig = get_oauth_config();
86
+ // Per-request override: the login/register layouts can encode a
87
+ // `?next=<path>` query param into the GoogleSignInButton callback URL,
88
+ // letting consumer flows (e.g. invite-link landings) keep the user on
89
+ // their original target after the OAuth round-trip rather than
90
+ // bouncing through the static `oauthConfig.default_redirect`.
91
+ //
92
+ // Validation: must be a same-origin path (starts with `/`, not `//`,
93
+ // no protocol). Anything else is dropped — prevents an open-redirect
94
+ // attack where a hostile referrer crafts a `next=//evil.com` URL.
95
+ const raw_next = request.nextUrl.searchParams.get("next");
96
+ const safe_next = raw_next &&
97
+ raw_next.startsWith("/") &&
98
+ !raw_next.startsWith("//") &&
99
+ !/^[a-z][a-z0-9+.\-]*:/i.test(raw_next)
100
+ ? raw_next
101
+ : null;
78
102
  // Check if user needs onboarding (no scope, no invitation = create firm)
79
103
  const hazoConnect = get_hazo_connect_instance();
80
104
  const { redirect_url: determined_redirect, needs_onboarding, invitation_check_skipped, invitation_table_error, } = await get_post_login_redirect(hazoConnect, user_id, email, {
81
- default_redirect: oauthConfig.default_redirect || loginConfig.redirectRoute || "/",
105
+ default_redirect: safe_next || oauthConfig.default_redirect || loginConfig.redirectRoute || "/",
82
106
  create_firm_url: oauthConfig.create_firm_url,
83
107
  skip_invitation_check: oauthConfig.skip_invitation_check,
84
- no_scope_redirect: oauthConfig.no_scope_redirect,
108
+ // When a per-request next= is set, override no_scope_redirect too.
109
+ // Otherwise an invite recipient (no scope yet) would still get
110
+ // bounced to /onboarding instead of back to the invite landing.
111
+ no_scope_redirect: safe_next || oauthConfig.no_scope_redirect,
85
112
  });
86
113
  // Log warning if invitation table is missing
87
114
  if (invitation_table_error) {
@@ -93,7 +120,7 @@ export async function GET(original_request) {
93
120
  note: "hazo_invitations table does not exist - run migration or set skip_invitation_check=true in [hazo_auth__oauth]",
94
121
  });
95
122
  }
96
- logger.info("google_callback_post_login_redirect", {
123
+ logger.debug("google_callback_post_login_redirect", {
97
124
  filename: get_filename(),
98
125
  line_number: get_line_number(),
99
126
  user_id,
@@ -16,6 +16,18 @@ export type LoginPageProps = {
16
16
  * Defaults to "#f1f5f9"
17
17
  */
18
18
  image_background_color?: string;
19
+ /**
20
+ * Layout mode (default: `"two_column"`).
21
+ * - `"two_column"` — full server-rendered page with the package's
22
+ * TwoColumnAuthLayout (image on the left, form on the right) wrapped in
23
+ * the standalone AuthPageShell. Backwards-compatible.
24
+ * - `"form_only"` — emits just the form content (no AuthPageShell, no
25
+ * TwoColumnAuthLayout). Use when wrapping the form in your own brand
26
+ * chrome (e.g. a custom split-panel layout or an existing app shell).
27
+ * `image_src` / `image_alt` / `image_background_color` are ignored in
28
+ * this mode.
29
+ */
30
+ layout?: "two_column" | "form_only";
19
31
  };
20
32
  /**
21
33
  * Zero-config LoginPage server component
@@ -40,6 +52,6 @@ export type LoginPageProps = {
40
52
  * @param props - Optional visual customization props
41
53
  * @returns Server-rendered login page
42
54
  */
43
- export default function LoginPage({ image_src, image_alt, image_background_color, }?: LoginPageProps): import("react/jsx-runtime").JSX.Element;
55
+ export default function LoginPage({ image_src, image_alt, image_background_color, layout, }?: LoginPageProps): import("react/jsx-runtime").JSX.Element;
44
56
  export { LoginPage };
45
57
  //# sourceMappingURL=login.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/server_pages/login.tsx"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAOrB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,cAAc,GAAG;IAC3B;;;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;AAGF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAChC,SAAS,EACT,SAAS,EACT,sBAAsB,GACvB,GAAE,cAAmB,2CAqCrB;AAGD,OAAO,EAAE,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/server_pages/login.tsx"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AASrB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,cAAc,GAAG;IAC3B;;;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;IAEhC;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,YAAY,GAAG,WAAW,CAAC;CACrC,CAAC;AAGF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAChC,SAAS,EACT,SAAS,EACT,sBAAsB,EACtB,MAAqB,GACtB,GAAE,cAAmB,2CA2DrB;AAGD,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -1,11 +1,13 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // file_description: Zero-config LoginPage server component - drop in and use with no configuration required
3
3
  // section: server-only-guard
4
4
  import "server-only";
5
5
  // section: imports
6
6
  import { get_login_config } from "../lib/login_config.server.js";
7
+ import { get_navbar_config } from "../lib/navbar_config.server.js";
7
8
  import { LoginClientWrapper } from "./login_client_wrapper.js";
8
9
  import { AuthPageShell } from "../components/layouts/shared/components/auth_page_shell.js";
10
+ import { FloatingHomeLink } from "../components/layouts/shared/components/floating_home_link.js";
9
11
  // section: component
10
12
  /**
11
13
  * Zero-config LoginPage server component
@@ -30,20 +32,34 @@ import { AuthPageShell } from "../components/layouts/shared/components/auth_page
30
32
  * @param props - Optional visual customization props
31
33
  * @returns Server-rendered login page
32
34
  */
33
- export default function LoginPage({ image_src, image_alt, image_background_color, } = {}) {
35
+ export default function LoginPage({ image_src, image_alt, image_background_color, layout = "two_column", } = {}) {
34
36
  // Load configuration from INI file (with defaults including asset images)
35
37
  const config = get_login_config();
36
38
  // Use props if provided, otherwise fall back to config (which includes default asset image)
37
39
  const finalImageSrc = image_src || config.imageSrc;
38
40
  const finalImageAlt = image_alt || config.imageAlt;
39
41
  const finalImageBackgroundColor = image_background_color || config.imageBackgroundColor;
40
- // Pass serializable config to client wrapper, wrapped in AuthPageShell for navbar support
41
- return (_jsx(AuthPageShell, { children: _jsx(LoginClientWrapper, { image_src: finalImageSrc, image_alt: finalImageAlt, image_background_color: finalImageBackgroundColor, redirectRoute: config.redirectRoute, successMessage: config.successMessage, alreadyLoggedInMessage: config.alreadyLoggedInMessage, showLogoutButton: config.showLogoutButton, showReturnHomeButton: config.showReturnHomeButton, returnHomeButtonLabel: config.returnHomeButtonLabel, returnHomePath: config.returnHomePath, forgotPasswordPath: config.forgotPasswordPath, forgotPasswordLabel: config.forgotPasswordLabel, createAccountPath: config.createAccountPath, createAccountLabel: config.createAccountLabel, showCreateAccountLink: config.showCreateAccountLink, oauth: {
42
- enable_google: config.oauth.enable_google,
43
- enable_email_password: config.oauth.enable_email_password,
44
- google_button_text: config.oauth.google_button_text,
45
- oauth_divider_text: config.oauth.oauth_divider_text,
46
- } }) }));
42
+ const wrapper = (_jsx(LoginClientWrapper, { image_src: finalImageSrc, image_alt: finalImageAlt, image_background_color: finalImageBackgroundColor, redirectRoute: config.redirectRoute, successMessage: config.successMessage, alreadyLoggedInMessage: config.alreadyLoggedInMessage, showLogoutButton: config.showLogoutButton, showReturnHomeButton: config.showReturnHomeButton, returnHomeButtonLabel: config.returnHomeButtonLabel, returnHomePath: config.returnHomePath, forgotPasswordPath: config.forgotPasswordPath, forgotPasswordLabel: config.forgotPasswordLabel, createAccountPath: config.createAccountPath, createAccountLabel: config.createAccountLabel, showCreateAccountLink: config.showCreateAccountLink, oauth: {
43
+ enable_google: config.oauth.enable_google,
44
+ enable_email_password: config.oauth.enable_email_password,
45
+ google_button_text: config.oauth.google_button_text,
46
+ oauth_divider_text: config.oauth.oauth_divider_text,
47
+ }, layout: layout }));
48
+ // form_only mode: skip AuthPageShell so the consumer's own page chrome
49
+ // (e.g. a split-panel AuthLayout) hosts the form without our standalone
50
+ // wrapper interfering. Two-column mode keeps the existing AuthPageShell
51
+ // wrap for navbar/centering support.
52
+ //
53
+ // The navbar config's home_link is also surfaced here in form_only mode
54
+ // so users mid-auth-flow always have a way out — without this, the only
55
+ // exit is the browser back button. Rendered as a fixed-position "Back to
56
+ // home" pill (top-left). Disabled by setting `[hazo_auth__navbar]
57
+ // show_home_link = false` or by hiding it CSS-side at the consumer.
58
+ if (layout === "form_only") {
59
+ const navbar = get_navbar_config();
60
+ return (_jsxs(_Fragment, { children: [navbar.show_home_link && (_jsx(FloatingHomeLink, { path: navbar.home_path, label: navbar.home_label })), wrapper] }));
61
+ }
62
+ return _jsx(AuthPageShell, { children: wrapper });
47
63
  }
48
64
  // Named export for direct imports
49
65
  export { LoginPage };
@@ -2,17 +2,19 @@ import type { LoginConfig } from "../lib/login_config.server";
2
2
  import type { OAuthLayoutConfig } from "../components/layouts/login/index";
3
3
  import type { StaticImageData } from "next/image";
4
4
  export type LoginClientWrapperProps = Omit<LoginConfig, 'imageSrc' | 'imageAlt' | 'imageBackgroundColor' | 'oauth' | 'showCreateAccountLink'> & {
5
- image_src: string | StaticImageData;
6
- image_alt: string;
7
- image_background_color: string;
5
+ image_src?: string | StaticImageData;
6
+ image_alt?: string;
7
+ image_background_color?: string;
8
8
  /** Show/hide "Create account" link (default: true) */
9
9
  showCreateAccountLink?: boolean;
10
10
  /** OAuth configuration */
11
11
  oauth?: OAuthLayoutConfig;
12
+ /** Layout mode — see LoginLayoutProps.layout. Default `"two_column"`. */
13
+ layout?: "two_column" | "form_only";
12
14
  };
13
15
  /**
14
16
  * Client wrapper for LoginLayout
15
17
  * Initializes hazo_connect data client on client side and passes config from server
16
18
  */
17
- export declare function LoginClientWrapper({ image_src, image_alt, image_background_color, redirectRoute, successMessage, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, forgotPasswordPath, forgotPasswordLabel, createAccountPath, createAccountLabel, showCreateAccountLink, oauth, }: LoginClientWrapperProps): import("react/jsx-runtime").JSX.Element;
19
+ export declare function LoginClientWrapper({ image_src, image_alt, image_background_color, redirectRoute, successMessage, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, forgotPasswordPath, forgotPasswordLabel, createAccountPath, createAccountLabel, showCreateAccountLink, oauth, layout, }: LoginClientWrapperProps): import("react/jsx-runtime").JSX.Element;
18
20
  //# sourceMappingURL=login_client_wrapper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"login_client_wrapper.d.ts","sourceRoot":"","sources":["../../src/server_pages/login_client_wrapper.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAG3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,UAAU,GAAG,sBAAsB,GAAG,OAAO,GAAG,uBAAuB,CAAC,GAAG;IAC9I,SAAS,EAAE,MAAM,GAAG,eAAe,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,sDAAsD;IACtD,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,0BAA0B;IAC1B,KAAK,CAAC,EAAE,iBAAiB,CAAC;CAC3B,CAAC;AAGF;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,SAAS,EACT,sBAAsB,EACtB,aAAa,EACb,cAAc,EACd,sBAAsB,EACtB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,qBAA4B,EAC5B,KAAK,GACN,EAAE,uBAAuB,2CAyCzB"}
1
+ {"version":3,"file":"login_client_wrapper.d.ts","sourceRoot":"","sources":["../../src/server_pages/login_client_wrapper.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAG3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,UAAU,GAAG,sBAAsB,GAAG,OAAO,GAAG,uBAAuB,CAAC,GAAG;IAC9I,SAAS,CAAC,EAAE,MAAM,GAAG,eAAe,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,sDAAsD;IACtD,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,0BAA0B;IAC1B,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,yEAAyE;IACzE,MAAM,CAAC,EAAE,YAAY,GAAG,WAAW,CAAC;CACrC,CAAC;AAGF;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,SAAS,EACT,sBAAsB,EACtB,aAAa,EACb,cAAc,EACd,sBAAsB,EACtB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,qBAA4B,EAC5B,KAAK,EACL,MAAqB,GACtB,EAAE,uBAAuB,2CA0CzB"}
@@ -11,7 +11,7 @@ import { create_sqlite_hazo_connect } from "../lib/hazo_connect_setup.js";
11
11
  * Client wrapper for LoginLayout
12
12
  * Initializes hazo_connect data client on client side and passes config from server
13
13
  */
14
- export function LoginClientWrapper({ image_src, image_alt, image_background_color, redirectRoute, successMessage, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, forgotPasswordPath, forgotPasswordLabel, createAccountPath, createAccountLabel, showCreateAccountLink = true, oauth, }) {
14
+ export function LoginClientWrapper({ image_src, image_alt, image_background_color, redirectRoute, successMessage, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, forgotPasswordPath, forgotPasswordLabel, createAccountPath, createAccountLabel, showCreateAccountLink = true, oauth, layout = "two_column", }) {
15
15
  const [dataClient, setDataClient] = useState(null);
16
16
  useEffect(() => {
17
17
  // Initialize hazo_connect on client side
@@ -23,5 +23,5 @@ export function LoginClientWrapper({ image_src, image_alt, image_background_colo
23
23
  if (!dataClient) {
24
24
  return (_jsx("div", { className: "cls_login_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(LoginLayout, { image_src: image_src, image_alt: image_alt, image_background_color: image_background_color, data_client: dataClient, redirectRoute: redirectRoute, successMessage: successMessage, alreadyLoggedInMessage: alreadyLoggedInMessage, showLogoutButton: showLogoutButton, showReturnHomeButton: showReturnHomeButton, returnHomeButtonLabel: returnHomeButtonLabel, returnHomePath: returnHomePath, forgot_password_path: forgotPasswordPath, forgot_password_label: forgotPasswordLabel, create_account_path: createAccountPath, create_account_label: createAccountLabel, show_create_account_link: showCreateAccountLink, oauth: oauth }));
26
+ return (_jsx(LoginLayout, { image_src: image_src, image_alt: image_alt, image_background_color: image_background_color, data_client: dataClient, redirectRoute: redirectRoute, successMessage: successMessage, alreadyLoggedInMessage: alreadyLoggedInMessage, showLogoutButton: showLogoutButton, showReturnHomeButton: showReturnHomeButton, returnHomeButtonLabel: returnHomeButtonLabel, returnHomePath: returnHomePath, forgot_password_path: forgotPasswordPath, forgot_password_label: forgotPasswordLabel, create_account_path: createAccountPath, create_account_label: createAccountLabel, show_create_account_link: showCreateAccountLink, oauth: oauth, layout: layout }));
27
27
  }
@@ -16,6 +16,12 @@ export type RegisterPageProps = {
16
16
  * Defaults from hazo_auth_config.ini or "#e2e8f0"
17
17
  */
18
18
  image_background_color?: string;
19
+ /**
20
+ * Layout mode (default: `"two_column"`). See `LoginPageProps.layout` for the
21
+ * full description. `"form_only"` skips both AuthPageShell and
22
+ * TwoColumnAuthLayout so the consumer's brand chrome can wrap the form.
23
+ */
24
+ layout?: "two_column" | "form_only";
19
25
  };
20
26
  /**
21
27
  * Zero-config RegisterPage server component
@@ -42,6 +48,6 @@ export type RegisterPageProps = {
42
48
  * @param props - Optional visual customization props
43
49
  * @returns Server-rendered register page
44
50
  */
45
- export default function RegisterPage({ image_src, image_alt, image_background_color, }?: RegisterPageProps): import("react/jsx-runtime").JSX.Element;
51
+ export default function RegisterPage({ image_src, image_alt, image_background_color, layout, }?: RegisterPageProps): import("react/jsx-runtime").JSX.Element;
46
52
  export { RegisterPage };
47
53
  //# sourceMappingURL=register.d.ts.map
@@ -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,2CAkCxB;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;AASrB,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;IAEhC;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,GAAG,WAAW,CAAC;CACrC,CAAC;AAIF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,SAAS,EACT,SAAS,EACT,sBAAsB,EACtB,MAAqB,GACtB,GAAE,iBAAsB,2CAiDxB;AAGD,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -1,11 +1,13 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // file_description: Zero-config RegisterPage server component - drop in and use with no configuration required
3
3
  // section: server-only-guard
4
4
  import "server-only";
5
5
  // section: imports
6
6
  import { get_register_config } from "../lib/register_config.server.js";
7
+ import { get_navbar_config } from "../lib/navbar_config.server.js";
7
8
  import { RegisterClientWrapper } from "./register_client_wrapper.js";
8
9
  import { AuthPageShell } from "../components/layouts/shared/components/auth_page_shell.js";
10
+ import { FloatingHomeLink } from "../components/layouts/shared/components/floating_home_link.js";
9
11
  // section: component
10
12
  /**
11
13
  * Zero-config RegisterPage server component
@@ -32,20 +34,27 @@ import { AuthPageShell } from "../components/layouts/shared/components/auth_page
32
34
  * @param props - Optional visual customization props
33
35
  * @returns Server-rendered register page
34
36
  */
35
- export default function RegisterPage({ image_src, image_alt, image_background_color, } = {}) {
37
+ export default function RegisterPage({ image_src, image_alt, image_background_color, layout = "two_column", } = {}) {
36
38
  // Load configuration from INI file (with defaults including asset images)
37
39
  const config = get_register_config();
38
40
  // Use props if provided, otherwise fall back to config (which includes default asset image)
39
41
  const finalImageSrc = image_src || config.imageSrc;
40
42
  const finalImageAlt = image_alt || config.imageAlt;
41
43
  const finalImageBackgroundColor = image_background_color || config.imageBackgroundColor;
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, 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
+ const wrapper = (_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: {
45
+ enable_google: config.oauth.enable_google,
46
+ enable_email_password: config.oauth.enable_email_password,
47
+ google_button_text: config.oauth.google_button_text,
48
+ oauth_divider_text: config.oauth.oauth_divider_text,
49
+ }, layout: layout }));
50
+ // form_only mode: skip AuthPageShell so the consumer's own page chrome
51
+ // hosts the form. See LoginPage for the full rationale, including the
52
+ // FloatingHomeLink rendering rule below.
53
+ if (layout === "form_only") {
54
+ const navbar = get_navbar_config();
55
+ return (_jsxs(_Fragment, { children: [navbar.show_home_link && (_jsx(FloatingHomeLink, { path: navbar.home_path, label: navbar.home_label })), wrapper] }));
56
+ }
57
+ return _jsx(AuthPageShell, { children: wrapper });
49
58
  }
50
59
  // Named export for direct imports
51
60
  export { RegisterPage };
@@ -2,15 +2,17 @@ import type { RegisterConfig } from "../lib/register_config.server";
2
2
  import type { OAuthLayoutConfig } from "../components/layouts/register/index";
3
3
  import type { StaticImageData } from "next/image";
4
4
  export type RegisterClientWrapperProps = Omit<RegisterConfig, 'imageSrc' | 'imageAlt' | 'imageBackgroundColor' | 'oauth'> & {
5
- image_src: string | StaticImageData;
6
- image_alt: string;
7
- image_background_color: string;
5
+ image_src?: string | StaticImageData;
6
+ image_alt?: string;
7
+ image_background_color?: string;
8
8
  /** OAuth configuration */
9
9
  oauth?: OAuthLayoutConfig;
10
+ /** Layout mode — see RegisterLayoutProps.layout. Default `"two_column"`. */
11
+ layout?: "two_column" | "form_only";
10
12
  };
11
13
  /**
12
14
  * Client wrapper for RegisterLayout
13
15
  * Initializes hazo_connect data client on client side and passes config from server
14
16
  */
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;
17
+ export declare function RegisterClientWrapper({ image_src, image_alt, image_background_color, showNameField, passwordRequirements, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, signInPath, signInLabel, oauth, layout, }: RegisterClientWrapperProps): import("react/jsx-runtime").JSX.Element;
16
18
  //# 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;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"}
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,CAAC,EAAE,MAAM,GAAG,eAAe,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,0BAA0B;IAC1B,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,4EAA4E;IAC5E,MAAM,CAAC,EAAE,YAAY,GAAG,WAAW,CAAC;CACrC,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,EACL,MAAqB,GACtB,EAAE,0BAA0B,2CAuC5B"}