@startsimpli/auth 0.4.13 → 0.4.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startsimpli/auth",
3
- "version": "0.4.13",
3
+ "version": "0.4.15",
4
4
  "description": "Shared authentication package for StartSimpli Next.js apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -53,6 +53,8 @@ export class AuthClient {
53
53
  tokenRefreshInterval: 4 * 60 * 1000, // 4 minutes
54
54
  onSessionExpired: () => {},
55
55
  onUnauthorized: () => {},
56
+ loginPath: '',
57
+ callbackParam: 'callbackUrl',
56
58
  ...config,
57
59
  };
58
60
  }
@@ -8,6 +8,7 @@ import {
8
8
  createContext,
9
9
  useContext,
10
10
  useEffect,
11
+ useRef,
11
12
  useState,
12
13
  useCallback,
13
14
  type ReactNode,
@@ -108,7 +109,20 @@ export function AuthProvider({
108
109
  };
109
110
  }, [authClient, initialSession]);
110
111
 
111
- // Session expiration handler covers both AuthClient timer and authFetch 401
112
+ // Keep refs current on every render so the stable handleExpired closure
113
+ // below always reads the latest config values without being in deps.
114
+ const loginPathRef = useRef(config.loginPath);
115
+ const callbackParamRef = useRef(config.callbackParam ?? 'callbackUrl');
116
+ const onSessionExpiredRef = useRef(config.onSessionExpired);
117
+ loginPathRef.current = config.loginPath;
118
+ callbackParamRef.current = config.callbackParam ?? 'callbackUrl';
119
+ onSessionExpiredRef.current = config.onSessionExpired;
120
+
121
+ // Session expiration handler — covers both AuthClient timer and authFetch 401.
122
+ // Depends only on authClient (a useState singleton that never changes after
123
+ // mount). config intentionally omitted: it's a new object literal on every
124
+ // render from the caller, so including it caused destroy() to fire and wipe
125
+ // the session on every re-render.
112
126
  useEffect(() => {
113
127
  const handleExpired = () => {
114
128
  setState({
@@ -116,18 +130,28 @@ export function AuthProvider({
116
130
  isLoading: false,
117
131
  isAuthenticated: false,
118
132
  });
119
- config.onSessionExpired?.();
133
+ onSessionExpiredRef.current?.();
134
+
135
+ if (loginPathRef.current && typeof window !== 'undefined') {
136
+ const here = window.location.pathname + window.location.search;
137
+ const isOnLogin = window.location.pathname.startsWith(loginPathRef.current);
138
+ if (!isOnLogin) {
139
+ const callback = encodeURIComponent(here);
140
+ window.location.href = `${loginPathRef.current}?${callbackParamRef.current}=${callback}`;
141
+ }
142
+ }
120
143
  };
121
144
 
145
+ // Wire AuthClient's internal expiry calls and the functional API (FetchWrapper).
122
146
  config.onSessionExpired = handleExpired;
123
- // Wire up the functional API's session expiration callback
124
147
  setOnSessionExpired(handleExpired);
125
148
 
126
149
  return () => {
127
150
  setOnSessionExpired(null);
128
151
  authClient.destroy();
129
152
  };
130
- }, [authClient, config]);
153
+ // eslint-disable-next-line react-hooks/exhaustive-deps
154
+ }, [authClient]);
131
155
 
132
156
  const login = useCallback(
133
157
  async (email: string, password: string) => {
@@ -111,6 +111,21 @@ export interface AuthConfig {
111
111
  tokenRefreshInterval?: number; // milliseconds, default 4 minutes
112
112
  onSessionExpired?: () => void;
113
113
  onUnauthorized?: () => void;
114
+ /**
115
+ * If set, AuthProvider redirects the browser here when the session is
116
+ * lost (refresh-token rejected, manual logout, etc.). The current path
117
+ * is appended as a query param so the login page can return the user.
118
+ * Same value the server-side middleware uses, e.g. `/auth/signin`.
119
+ * Without this set, session loss only resets React state and the user
120
+ * can be left on a page where every subsequent request silently 403s
121
+ * (raise-simpli-lxv).
122
+ */
123
+ loginPath?: string;
124
+ /**
125
+ * Query-param name appended to `loginPath` to carry the return URL.
126
+ * Defaults to `callbackUrl` to match the shared server middleware.
127
+ */
128
+ callbackParam?: string;
114
129
  }
115
130
 
116
131
  /**