@ttt-productions/auth-core 1.6.6 → 1.6.7

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,5 +1,11 @@
1
1
  export interface AuthGuardConfig {
2
- /** Routes accessible without authentication */
2
+ /**
3
+ * Routes accessible without authentication, matched by PREFIX
4
+ * (`pathname.startsWith(entry)`), so `'/share/'` covers every `/share/:id`
5
+ * beneath it and `'/terms'` covers `/terms/anything`. This is the SOLE source of public
6
+ * routes — the hook has no built-in defaults and no app-specific literals, so
7
+ * a consumer that needs a dynamic public section must list its prefix here.
8
+ */
3
9
  publicRoutes: string[];
4
10
  /** Routes that redirect authenticated users away (login, register) */
5
11
  authRedirectRoutes: string[];
@@ -30,6 +36,11 @@ export interface AuthGuardConfig {
30
36
  * - Redirects unauthenticated users from protected routes to loginRoute.
31
37
  * - Redirects authenticated users from authRedirectRoutes to defaultRoute.
32
38
  * - Saves attempted path for post-login redirect.
39
+ *
40
+ * "Public" is decided ENTIRELY by `config.publicRoutes` (prefix match). The hook
41
+ * carries no built-in or implicit public route: every route a consumer wants
42
+ * reachable while signed out — including a dynamic section like `/share/:id` — is
43
+ * listed by that consumer as a prefix.
33
44
  */
34
45
  export declare function useAuthGuard(config: AuthGuardConfig): void;
35
46
  //# sourceMappingURL=useAuthGuard.d.ts.map
@@ -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,CA4E1D"}
1
+ {"version":3,"file":"useAuthGuard.d.ts","sourceRoot":"","sources":["../../src/react/useAuthGuard.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,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;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CA2E1D"}
@@ -8,6 +8,11 @@ const DEFAULT_REDIRECT_KEY = "auth_redirect_path";
8
8
  * - Redirects unauthenticated users from protected routes to loginRoute.
9
9
  * - Redirects authenticated users from authRedirectRoutes to defaultRoute.
10
10
  * - Saves attempted path for post-login redirect.
11
+ *
12
+ * "Public" is decided ENTIRELY by `config.publicRoutes` (prefix match). The hook
13
+ * carries no built-in or implicit public route: every route a consumer wants
14
+ * reachable while signed out — including a dynamic section like `/share/:id` — is
15
+ * listed by that consumer as a prefix.
11
16
  */
12
17
  export function useAuthGuard(config) {
13
18
  const { publicRoutes, authRedirectRoutes, loginRoute, defaultRoute, rootRedirect, redirectKey = DEFAULT_REDIRECT_KEY, replace, pathname, loading, isAuthenticated, } = config;
@@ -16,9 +21,9 @@ export function useAuthGuard(config) {
16
21
  // asynchronously (the router fetches the target route first), so this effect can
17
22
  // legitimately re-run while pathname still reads the auth route (an auth-state
18
23
  // 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.
24
+ // re-replaces to defaultRoute, CLOBBERING the in-flight saved-path navigation —
25
+ // a deep link that routed through login then landed on defaultRoute instead of
26
+ // the saved path. The latch resets whenever the pathname is off the auth-route set.
22
27
  const authRouteRedirected = useRef(false);
23
28
  useEffect(() => {
24
29
  if (loading)
@@ -31,7 +36,7 @@ export function useAuthGuard(config) {
31
36
  replace(isAuthenticated ? rootRedirect.authenticated : rootRedirect.unauthenticated);
32
37
  return;
33
38
  }
34
- const isPublic = publicRoutes.some((r) => pathname.startsWith(r)) || pathname.startsWith("/v/");
39
+ const isPublic = publicRoutes.some((r) => pathname.startsWith(r));
35
40
  const isAuthRedirect = authRedirectRoutes.includes(pathname);
36
41
  // Unauthenticated on protected route -> save path, go to login
37
42
  if (!isAuthenticated && !isPublic) {
@@ -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,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"]}
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;AA+B1C,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;AAElD;;;;;;;;;;;;GAYG;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,gFAAgF;IAChF,+EAA+E;IAC/E,oFAAoF;IACpF,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,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAClE,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 /**\n * Routes accessible without authentication, matched by PREFIX\n * (`pathname.startsWith(entry)`), so `'/share/'` covers every `/share/:id`\n * beneath it and `'/terms'` covers `/terms/anything`. This is the SOLE source of public\n * routes — the hook has no built-in defaults and no app-specific literals, so\n * a consumer that needs a dynamic public section must list its prefix here.\n */\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 *\n * \"Public\" is decided ENTIRELY by `config.publicRoutes` (prefix match). The hook\n * carries no built-in or implicit public route: every route a consumer wants\n * reachable while signed out — including a dynamic section like `/share/:id` — is\n * listed by that consumer as a prefix.\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 // a deep link that routed through login then landed on defaultRoute instead of\n // the saved path. 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 = publicRoutes.some((r) => pathname.startsWith(r));\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"]}
@@ -11,8 +11,8 @@
11
11
  * throw err;
12
12
  * }
13
13
  *
14
- * WHY THE PACKAGE MUST NOT BUILD THE HttpsError ITSELF (2026-07-20, live on hosted
15
- * dev): firebase-functions 7.2.5 ships TWO distinct `HttpsError` classes — the one
14
+ * WHY THE PACKAGE MUST NOT BUILD THE HttpsError ITSELF: firebase-functions 7.2.5
15
+ * ships TWO distinct `HttpsError` classes — the one
16
16
  * exported from the ESM entry `firebase-functions/v2/https`, and the one the CJS
17
17
  * callable runtime closes over and `instanceof`-checks at
18
18
  * `lib/common/providers/https.js`. An `HttpsError` constructed by this package's
@@ -25,13 +25,13 @@
25
25
  *
26
26
  * Because `assertAuth` is the first line of every callable, that turned EVERY auth
27
27
  * rejection — unauthenticated, unverified email, suspended, banned — into a generic
28
- * 500: users saw "An Error Occurred" instead of the verify-email dialog, and Sentry
28
+ * 500: users saw a generic error instead of the verify-email dialog, and monitoring
29
29
  * filled with 500s that were really expected answers. Constructing the HttpsError in
30
30
  * APP module space fixes the identity permanently and cannot regress when
31
31
  * firebase-functions repackages.
32
32
  *
33
33
  * This also brings auth-core in line with engineering-rules QUALITY-102 (generic
34
- * packages stay framework-agnostic; the TTT-side adapter binds the framework) and
34
+ * packages stay framework-agnostic; the consuming app's adapter binds the framework) and
35
35
  * mirrors `report-core`'s `ReportCoreTaskError`, which already works this way.
36
36
  */
37
37
  export type AuthAssertionErrorCode = "unauthenticated" | "failed-precondition" | "not-found" | "permission-denied";
@@ -1 +1 @@
1
- {"version":3,"file":"authError.js","sourceRoot":"","sources":["../../src/server/authError.ts"],"names":[],"mappings":"AA0CA,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,IAAI,CAAyB;IAEtC,YAAY,IAA4B,EAAE,OAAe;QACvD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF","sourcesContent":["/**\n * Typed failure channel for `createAssertAuth`.\n *\n * Every EXPECTED auth outcome (not signed in, email unverified, missing profile,\n * account not in good standing) throws an `AuthAssertionError` whose `code` is\n * deliberately the matching `HttpsError` code string, so a consuming Cloud\n * Function maps it 1:1 at the app boundary:\n *\n * catch (err) {\n * if (err instanceof AuthAssertionError) throw new HttpsError(err.code, err.message);\n * throw err;\n * }\n *\n * WHY THE PACKAGE MUST NOT BUILD THE HttpsError ITSELF (2026-07-20, live on hosted\n * dev): firebase-functions 7.2.5 ships TWO distinct `HttpsError` classes — the one\n * exported from the ESM entry `firebase-functions/v2/https`, and the one the CJS\n * callable runtime closes over and `instanceof`-checks at\n * `lib/common/providers/https.js`. An `HttpsError` constructed by this package's\n * ESM `dist` fails that identity check, so `onCall` logged \"Unhandled error\" and\n * replaced a correct 400 with `500 INTERNAL`. Measured:\n *\n * ESM class === CJS class ? false\n * esm-built error instanceof CJS class ? false\n * httpErrorCode.status: 400 (the error itself was correct)\n *\n * Because `assertAuth` is the first line of every callable, that turned EVERY auth\n * rejection — unauthenticated, unverified email, suspended, banned — into a generic\n * 500: users saw \"An Error Occurred\" instead of the verify-email dialog, and Sentry\n * filled with 500s that were really expected answers. Constructing the HttpsError in\n * APP module space fixes the identity permanently and cannot regress when\n * firebase-functions repackages.\n *\n * This also brings auth-core in line with engineering-rules QUALITY-102 (generic\n * packages stay framework-agnostic; the TTT-side adapter binds the framework) and\n * mirrors `report-core`'s `ReportCoreTaskError`, which already works this way.\n */\nexport type AuthAssertionErrorCode =\n | \"unauthenticated\"\n | \"failed-precondition\"\n | \"not-found\"\n | \"permission-denied\";\n\nexport class AuthAssertionError extends Error {\n readonly code: AuthAssertionErrorCode;\n\n constructor(code: AuthAssertionErrorCode, message: string) {\n super(message);\n this.name = \"AuthAssertionError\";\n this.code = code;\n }\n}\n"]}
1
+ {"version":3,"file":"authError.js","sourceRoot":"","sources":["../../src/server/authError.ts"],"names":[],"mappings":"AA0CA,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,IAAI,CAAyB;IAEtC,YAAY,IAA4B,EAAE,OAAe;QACvD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF","sourcesContent":["/**\n * Typed failure channel for `createAssertAuth`.\n *\n * Every EXPECTED auth outcome (not signed in, email unverified, missing profile,\n * account not in good standing) throws an `AuthAssertionError` whose `code` is\n * deliberately the matching `HttpsError` code string, so a consuming Cloud\n * Function maps it 1:1 at the app boundary:\n *\n * catch (err) {\n * if (err instanceof AuthAssertionError) throw new HttpsError(err.code, err.message);\n * throw err;\n * }\n *\n * WHY THE PACKAGE MUST NOT BUILD THE HttpsError ITSELF: firebase-functions 7.2.5\n * ships TWO distinct `HttpsError` classes — the one\n * exported from the ESM entry `firebase-functions/v2/https`, and the one the CJS\n * callable runtime closes over and `instanceof`-checks at\n * `lib/common/providers/https.js`. An `HttpsError` constructed by this package's\n * ESM `dist` fails that identity check, so `onCall` logged \"Unhandled error\" and\n * replaced a correct 400 with `500 INTERNAL`. Measured:\n *\n * ESM class === CJS class ? false\n * esm-built error instanceof CJS class ? false\n * httpErrorCode.status: 400 (the error itself was correct)\n *\n * Because `assertAuth` is the first line of every callable, that turned EVERY auth\n * rejection — unauthenticated, unverified email, suspended, banned — into a generic\n * 500: users saw a generic error instead of the verify-email dialog, and monitoring\n * filled with 500s that were really expected answers. Constructing the HttpsError in\n * APP module space fixes the identity permanently and cannot regress when\n * firebase-functions repackages.\n *\n * This also brings auth-core in line with engineering-rules QUALITY-102 (generic\n * packages stay framework-agnostic; the consuming app's adapter binds the framework) and\n * mirrors `report-core`'s `ReportCoreTaskError`, which already works this way.\n */\nexport type AuthAssertionErrorCode =\n | \"unauthenticated\"\n | \"failed-precondition\"\n | \"not-found\"\n | \"permission-denied\";\n\nexport class AuthAssertionError extends Error {\n readonly code: AuthAssertionErrorCode;\n\n constructor(code: AuthAssertionErrorCode, message: string) {\n super(message);\n this.name = \"AuthAssertionError\";\n this.code = code;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/auth-core",
3
- "version": "1.6.6",
3
+ "version": "1.6.7",
4
4
  "description": "Shared Firebase authentication utilities and React hooks for TTT Productions apps",
5
5
  "repository": {
6
6
  "type": "git",