@ttt-productions/auth-core 1.6.2 → 1.6.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"useAuthGuard.d.ts","sourceRoot":"","sources":["../../src/react/useAuthGuard.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,sEAAsE;IACtE,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,YAAY,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,mBAAmB;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,qBAAqB;IACrB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAID;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CA4D1D"}
1
+ {"version":3,"file":"useAuthGuard.d.ts","sourceRoot":"","sources":["../../src/react/useAuthGuard.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,sEAAsE;IACtE,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,YAAY,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,mBAAmB;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,qBAAqB;IACrB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAID;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CA4E1D"}
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { useEffect } from "react";
2
+ import { useEffect, useRef } from "react";
3
3
  const DEFAULT_REDIRECT_KEY = "auth_redirect_path";
4
4
  /**
5
5
  * Handles route protection. Call in app shell component.
@@ -11,9 +11,21 @@ const DEFAULT_REDIRECT_KEY = "auth_redirect_path";
11
11
  */
12
12
  export function useAuthGuard(config) {
13
13
  const { publicRoutes, authRedirectRoutes, loginRoute, defaultRoute, rootRedirect, redirectKey = DEFAULT_REDIRECT_KEY, replace, pathname, loading, isAuthenticated, } = config;
14
+ // The auth-route redirect below is SINGLE-SHOT per auth-route visit. That branch
15
+ // CONSUMES redirectKey and issues a navigation, but the navigation commits
16
+ // asynchronously (the router fetches the target route first), so this effect can
17
+ // legitimately re-run while pathname still reads the auth route (an auth-state
18
+ // re-render, a loading flap). A re-run then finds the key already gone and
19
+ // re-replaces to defaultRoute, CLOBBERING the in-flight saved-path navigation
20
+ // (live: TTT hosted path 13 MISC-51 — a /v share-link login landed on /landing).
21
+ // The latch resets whenever the pathname is off the auth-route set.
22
+ const authRouteRedirected = useRef(false);
14
23
  useEffect(() => {
15
24
  if (loading)
16
25
  return;
26
+ if (!authRedirectRoutes.includes(pathname)) {
27
+ authRouteRedirected.current = false;
28
+ }
17
29
  // Root path special handling
18
30
  if (rootRedirect && pathname === "/") {
19
31
  replace(isAuthenticated ? rootRedirect.authenticated : rootRedirect.unauthenticated);
@@ -31,6 +43,9 @@ export function useAuthGuard(config) {
31
43
  }
32
44
  // Authenticated user on auth-only route (login, register, etc.)
33
45
  if (isAuthenticated && isAuthRedirect) {
46
+ if (authRouteRedirected.current)
47
+ return;
48
+ authRouteRedirected.current = true;
34
49
  let target = defaultRoute;
35
50
  if (typeof window !== "undefined") {
36
51
  const saved = localStorage.getItem(redirectKey);
@@ -1 +1 @@
1
- {"version":3,"file":"useAuthGuard.js","sourceRoot":"","sources":["../../src/react/useAuthGuard.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAyBlC,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,MAAuB;IAClD,MAAM,EACJ,YAAY,EACZ,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,GAAG,oBAAoB,EAClC,OAAO,EACP,QAAQ,EACR,OAAO,EACP,eAAe,GAChB,GAAG,MAAM,CAAC;IAEX,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO;YAAE,OAAO;QAEpB,6BAA6B;QAC7B,IAAI,YAAY,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;YACrC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;YACrF,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GACZ,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACjF,MAAM,cAAc,GAAG,kBAAkB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAE7D,+DAA+D;QAC/D,IAAI,CAAC,eAAe,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClC,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;gBAClC,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;YAC9C,CAAC;YACD,OAAO,CAAC,UAAU,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QAED,gEAAgE;QAChE,IAAI,eAAe,IAAI,cAAc,EAAE,CAAC;YACtC,IAAI,MAAM,GAAG,YAAY,CAAC;YAC1B,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;gBAClC,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;gBAChD,IAAI,KAAK,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;oBACjD,MAAM,GAAG,KAAK,CAAC;gBACjB,CAAC;gBACD,YAAY,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;YACvC,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,EAAE;QACD,OAAO;QACP,eAAe;QACf,QAAQ;QACR,YAAY;QACZ,kBAAkB;QAClB,UAAU;QACV,YAAY;QACZ,YAAY;QACZ,WAAW;QACX,OAAO;KACR,CAAC,CAAC;AACL,CAAC","sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\n\nexport interface AuthGuardConfig {\n /** Routes accessible without authentication */\n publicRoutes: string[];\n /** Routes that redirect authenticated users away (login, register) */\n authRedirectRoutes: string[];\n /** Where unauthenticated users go */\n loginRoute: string;\n /** Where authenticated users go from authRedirectRoutes */\n defaultRoute: string;\n /** Special root path handling */\n rootRedirect?: { authenticated: string; unauthenticated: string };\n /** localStorage key for post-login redirect */\n redirectKey?: string;\n /** Navigation function (e.g. router.replace) */\n replace: (path: string) => void;\n /** Current path */\n pathname: string;\n /** From useAuth() */\n loading: boolean;\n /** From useAuth() */\n isAuthenticated: boolean;\n}\n\nconst DEFAULT_REDIRECT_KEY = \"auth_redirect_path\";\n\n/**\n * Handles route protection. Call in app shell component.\n *\n * - Does nothing while loading.\n * - Redirects unauthenticated users from protected routes to loginRoute.\n * - Redirects authenticated users from authRedirectRoutes to defaultRoute.\n * - Saves attempted path for post-login redirect.\n */\nexport function useAuthGuard(config: AuthGuardConfig): void {\n const {\n publicRoutes,\n authRedirectRoutes,\n loginRoute,\n defaultRoute,\n rootRedirect,\n redirectKey = DEFAULT_REDIRECT_KEY,\n replace,\n pathname,\n loading,\n isAuthenticated,\n } = config;\n\n useEffect(() => {\n if (loading) return;\n\n // Root path special handling\n if (rootRedirect && pathname === \"/\") {\n replace(isAuthenticated ? rootRedirect.authenticated : rootRedirect.unauthenticated);\n return;\n }\n\n const isPublic =\n publicRoutes.some((r) => pathname.startsWith(r)) || pathname.startsWith(\"/v/\");\n const isAuthRedirect = authRedirectRoutes.includes(pathname);\n\n // Unauthenticated on protected route -> save path, go to login\n if (!isAuthenticated && !isPublic) {\n if (typeof window !== \"undefined\") {\n localStorage.setItem(redirectKey, pathname);\n }\n replace(loginRoute);\n return;\n }\n\n // Authenticated user on auth-only route (login, register, etc.)\n if (isAuthenticated && isAuthRedirect) {\n let target = defaultRoute;\n if (typeof window !== \"undefined\") {\n const saved = localStorage.getItem(redirectKey);\n if (saved && !authRedirectRoutes.includes(saved)) {\n target = saved;\n }\n localStorage.removeItem(redirectKey);\n }\n replace(target);\n }\n }, [\n loading,\n isAuthenticated,\n pathname,\n publicRoutes,\n authRedirectRoutes,\n loginRoute,\n defaultRoute,\n rootRedirect,\n redirectKey,\n replace,\n ]);\n}\n"]}
1
+ {"version":3,"file":"useAuthGuard.js","sourceRoot":"","sources":["../../src/react/useAuthGuard.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAyB1C,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,MAAuB;IAClD,MAAM,EACJ,YAAY,EACZ,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,GAAG,oBAAoB,EAClC,OAAO,EACP,QAAQ,EACR,OAAO,EACP,eAAe,GAChB,GAAG,MAAM,CAAC;IAEX,iFAAiF;IACjF,2EAA2E;IAC3E,iFAAiF;IACjF,+EAA+E;IAC/E,2EAA2E;IAC3E,8EAA8E;IAC9E,iFAAiF;IACjF,oEAAoE;IACpE,MAAM,mBAAmB,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE1C,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO;YAAE,OAAO;QAEpB,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3C,mBAAmB,CAAC,OAAO,GAAG,KAAK,CAAC;QACtC,CAAC;QAED,6BAA6B;QAC7B,IAAI,YAAY,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;YACrC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;YACrF,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GACZ,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACjF,MAAM,cAAc,GAAG,kBAAkB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAE7D,+DAA+D;QAC/D,IAAI,CAAC,eAAe,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClC,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;gBAClC,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;YAC9C,CAAC;YACD,OAAO,CAAC,UAAU,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QAED,gEAAgE;QAChE,IAAI,eAAe,IAAI,cAAc,EAAE,CAAC;YACtC,IAAI,mBAAmB,CAAC,OAAO;gBAAE,OAAO;YACxC,mBAAmB,CAAC,OAAO,GAAG,IAAI,CAAC;YACnC,IAAI,MAAM,GAAG,YAAY,CAAC;YAC1B,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;gBAClC,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;gBAChD,IAAI,KAAK,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;oBACjD,MAAM,GAAG,KAAK,CAAC;gBACjB,CAAC;gBACD,YAAY,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;YACvC,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,EAAE;QACD,OAAO;QACP,eAAe;QACf,QAAQ;QACR,YAAY;QACZ,kBAAkB;QAClB,UAAU;QACV,YAAY;QACZ,YAAY;QACZ,WAAW;QACX,OAAO;KACR,CAAC,CAAC;AACL,CAAC","sourcesContent":["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\n\nexport interface AuthGuardConfig {\n /** Routes accessible without authentication */\n publicRoutes: string[];\n /** Routes that redirect authenticated users away (login, register) */\n authRedirectRoutes: string[];\n /** Where unauthenticated users go */\n loginRoute: string;\n /** Where authenticated users go from authRedirectRoutes */\n defaultRoute: string;\n /** Special root path handling */\n rootRedirect?: { authenticated: string; unauthenticated: string };\n /** localStorage key for post-login redirect */\n redirectKey?: string;\n /** Navigation function (e.g. router.replace) */\n replace: (path: string) => void;\n /** Current path */\n pathname: string;\n /** From useAuth() */\n loading: boolean;\n /** From useAuth() */\n isAuthenticated: boolean;\n}\n\nconst DEFAULT_REDIRECT_KEY = \"auth_redirect_path\";\n\n/**\n * Handles route protection. Call in app shell component.\n *\n * - Does nothing while loading.\n * - Redirects unauthenticated users from protected routes to loginRoute.\n * - Redirects authenticated users from authRedirectRoutes to defaultRoute.\n * - Saves attempted path for post-login redirect.\n */\nexport function useAuthGuard(config: AuthGuardConfig): void {\n const {\n publicRoutes,\n authRedirectRoutes,\n loginRoute,\n defaultRoute,\n rootRedirect,\n redirectKey = DEFAULT_REDIRECT_KEY,\n replace,\n pathname,\n loading,\n isAuthenticated,\n } = config;\n\n // The auth-route redirect below is SINGLE-SHOT per auth-route visit. That branch\n // CONSUMES redirectKey and issues a navigation, but the navigation commits\n // asynchronously (the router fetches the target route first), so this effect can\n // legitimately re-run while pathname still reads the auth route (an auth-state\n // re-render, a loading flap). A re-run then finds the key already gone and\n // re-replaces to defaultRoute, CLOBBERING the in-flight saved-path navigation\n // (live: TTT hosted path 13 MISC-51 — a /v share-link login landed on /landing).\n // The latch resets whenever the pathname is off the auth-route set.\n const authRouteRedirected = useRef(false);\n\n useEffect(() => {\n if (loading) return;\n\n if (!authRedirectRoutes.includes(pathname)) {\n authRouteRedirected.current = false;\n }\n\n // Root path special handling\n if (rootRedirect && pathname === \"/\") {\n replace(isAuthenticated ? rootRedirect.authenticated : rootRedirect.unauthenticated);\n return;\n }\n\n const isPublic =\n publicRoutes.some((r) => pathname.startsWith(r)) || pathname.startsWith(\"/v/\");\n const isAuthRedirect = authRedirectRoutes.includes(pathname);\n\n // Unauthenticated on protected route -> save path, go to login\n if (!isAuthenticated && !isPublic) {\n if (typeof window !== \"undefined\") {\n localStorage.setItem(redirectKey, pathname);\n }\n replace(loginRoute);\n return;\n }\n\n // Authenticated user on auth-only route (login, register, etc.)\n if (isAuthenticated && isAuthRedirect) {\n if (authRouteRedirected.current) return;\n authRouteRedirected.current = true;\n let target = defaultRoute;\n if (typeof window !== \"undefined\") {\n const saved = localStorage.getItem(redirectKey);\n if (saved && !authRedirectRoutes.includes(saved)) {\n target = saved;\n }\n localStorage.removeItem(redirectKey);\n }\n replace(target);\n }\n }, [\n loading,\n isAuthenticated,\n pathname,\n publicRoutes,\n authRedirectRoutes,\n loginRoute,\n defaultRoute,\n rootRedirect,\n redirectKey,\n replace,\n ]);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/auth-core",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "description": "Shared Firebase authentication utilities and React hooks for TTT Productions apps",
5
5
  "repository": {
6
6
  "type": "git",