@autobusal/providers 1.2.11 → 1.2.12

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/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ All notable changes to `@autobusal/providers` are documented here. This project
4
4
  adheres to [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.2.12] - 2026-07-24
8
+
9
+ ### Fixed
10
+
11
+ - The full user profile - name, email, and via the nested admin/operator/agent/subagent/employee/driver/visitor sub-object: phone, address, passport, date of birth, company/NIPT - was persisted verbatim to localStorage, readable at rest by any script on the origin. Only a display-only subset (id, name, name_display, type, status) is now persisted; the full object still lives in-memory during an active session exactly as before. A new bootstrap fetch (wired into Setup) re-hydrates the full profile once per app load using the same reduced signal, guarded so it never fires for a genuine guest.
12
+
7
13
  ## [1.2.11] - 2026-07-19
8
14
 
9
15
  ### Fixed
package/Setup/Setup.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import Preload from './Preload';
2
2
  import languages from './languages';
3
3
  import analytics from './analytics';
4
+ import useUserBootstrap from './useUserBootstrap';
4
5
  import { useGetSettings, useGetMenu } from '../services';
5
6
 
6
7
  interface Props {
@@ -12,6 +13,7 @@ const Setup = ({ type, children }: Props): JSX.Element => {
12
13
  const { data } = useGetSettings();
13
14
 
14
15
  useGetMenu();
16
+ useUserBootstrap();
15
17
 
16
18
  languages(type, data.preferences.languages.default);
17
19
 
@@ -0,0 +1,52 @@
1
+ import { useEffect } from 'react';
2
+ import { useQuery } from '@tanstack/react-query';
3
+ import apiClient, { isBffMode } from '../Queries/apiClient';
4
+ import { useUserStore } from '../stores/user';
5
+ import { UserData } from '../types/users';
6
+
7
+ // Edited: Ferjolt Ozuni - Date: 2026-07-24
8
+ // The user store now only persists a display-only subset to localStorage
9
+ // (see stores/user.ts) - the full profile (nested role data: phone, address,
10
+ // passport, company, etc.) is no longer restored on a cold page load. This
11
+ // hook re-fetches it once per app mount, so any component reading
12
+ // `data.visitor.phone` etc. gets it back a moment after load instead of
13
+ // instantly - the only user-visible change from this fix.
14
+ //
15
+ // This must NOT fire for a genuine guest: apiClient's response interceptor
16
+ // treats any 401 as "session is gone" and redirects to /account/logout
17
+ // (bearer mode immediately, bff mode after confirming via /session) - firing
18
+ // this unconditionally would send every anonymous visitor there. It only
19
+ // runs when there's an actual signal of a prior login: the bearer token
20
+ // (bearer mode) or the redacted user stub this same store persists (bff
21
+ // mode, where the token itself is an httpOnly cookie invisible to JS).
22
+ const hasLoginSignal = (): boolean => (
23
+ (!isBffMode && localStorage.getItem('token') !== null)
24
+ || localStorage.getItem('user') !== null
25
+ );
26
+
27
+ const useUserBootstrap = (): void => {
28
+ const user = useUserStore();
29
+
30
+ const enabled = hasLoginSignal();
31
+
32
+ const { data } = useQuery<UserData>({
33
+ queryKey: ['account-bootstrap'],
34
+ queryFn: async () => (
35
+ await apiClient
36
+ .get('/api/account/refresh')
37
+ .then(response => response.data)
38
+ ),
39
+ enabled,
40
+ retry: false,
41
+ staleTime: Infinity
42
+ });
43
+
44
+ useEffect(() => {
45
+ if (data) {
46
+ user.save(data);
47
+ }
48
+ // eslint-disable-next-line react-hooks/exhaustive-deps
49
+ }, [data]);
50
+ };
51
+
52
+ export default useUserBootstrap;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/providers",
3
- "version": "1.2.11",
3
+ "version": "1.2.12",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"
package/stores/user.ts CHANGED
@@ -7,19 +7,45 @@ export interface UserStore {
7
7
  remove: () => void
8
8
  }
9
9
 
10
- const initial = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user') ?? '{}') : undefined;
10
+ // Edited: Ferjolt Ozuni - Date: 2026-07-24
11
+ // Bug: the full UserData object - name, email, and (via the nested
12
+ // admin/operator/agent/subagent/employee/driver/visitor sub-object) phone,
13
+ // address, passport, date of birth, company/NIPT - was persisted verbatim to
14
+ // localStorage, readable at rest by any script on the origin (any XSS,
15
+ // a malicious browser extension, disk/backup access on a shared machine).
16
+ // Fix: only a display-only subset - enough to paint the UI immediately on
17
+ // load - is ever written to disk. The full object still lives in the
18
+ // in-memory store during an active session exactly as before (nothing
19
+ // changes for any component reading `data.visitor.phone` etc. right after
20
+ // login/activate/refresh); it's just never written to disk. A bootstrap
21
+ // fetch (see Setup/useUserBootstrap.ts) re-hydrates the full in-memory
22
+ // object shortly after app load using this same reduced signal.
23
+ type PersistedUser = Pick<UserData, 'id' | 'name' | 'name_display' | 'type' | 'status'>;
24
+
25
+ const STORAGE_KEY = 'user';
26
+
27
+ const redact = (user: UserData): PersistedUser => ({
28
+ id: user.id,
29
+ name: user.name,
30
+ name_display: user.name_display,
31
+ type: user.type,
32
+ status: user.status
33
+ });
34
+
35
+ const stored = localStorage.getItem(STORAGE_KEY);
36
+ const initial: PersistedUser | undefined = stored ? JSON.parse(stored) : undefined;
11
37
 
12
38
  export const useUserStore = create<UserStore>((set) => ({
13
- data: initial,
39
+ data: initial as UserData | undefined,
14
40
  save: (user) => set(() => {
15
- localStorage.setItem('user', JSON.stringify(user));
41
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(redact(user)));
16
42
 
17
43
  return {
18
44
  data: user
19
45
  };
20
46
  }),
21
47
  remove: () => set(() => {
22
- localStorage.removeItem('user');
48
+ localStorage.removeItem(STORAGE_KEY);
23
49
 
24
50
  return {
25
51
  data: undefined