@sudobility/auth_lib 0.0.12 → 0.0.16

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,9 @@
1
1
  /**
2
2
  * @fileoverview Admin utilities exports
3
+ *
4
+ * Re-exports admin utilities from @sudobility/types for backwards compatibility.
5
+ * These utilities are now defined in @sudobility/types to be shared between
6
+ * frontend (auth_lib) and backend (auth_service) packages.
3
7
  */
4
- export { parseAdminEmails, isAdminEmail, createAdminChecker, } from './admin-emails';
8
+ export { parseAdminEmails, isAdminEmail, createAdminChecker, } from '@sudobility/types';
5
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,GACnB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,GACnB,MAAM,mBAAmB,CAAC"}
@@ -1,4 +1,8 @@
1
1
  /**
2
2
  * @fileoverview Admin utilities exports
3
+ *
4
+ * Re-exports admin utilities from @sudobility/types for backwards compatibility.
5
+ * These utilities are now defined in @sudobility/types to be shared between
6
+ * frontend (auth_lib) and backend (auth_service) packages.
3
7
  */
4
- export { parseAdminEmails, isAdminEmail, createAdminChecker, } from './admin-emails';
8
+ export { parseAdminEmails, isAdminEmail, createAdminChecker, } from '@sudobility/types';
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * @fileoverview Hooks exports
3
3
  */
4
- export { useFirebaseAuthNetworkClient } from './useFirebaseAuthNetworkClient';
4
+ export { useFirebaseAuthNetworkClient, createFirebaseAuthNetworkClient, } from './useFirebaseAuthNetworkClient';
5
+ export { useSiteAdmin, siteAdminQueryKey, type UseSiteAdminOptions, type UseSiteAdminResult, type UserInfoResponse, } from './useSiteAdmin';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,4BAA4B,EAC5B,+BAA+B,GAChC,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC"}
@@ -1,4 +1,5 @@
1
1
  /**
2
2
  * @fileoverview Hooks exports
3
3
  */
4
- export { useFirebaseAuthNetworkClient } from './useFirebaseAuthNetworkClient';
4
+ export { useFirebaseAuthNetworkClient, createFirebaseAuthNetworkClient, } from './useFirebaseAuthNetworkClient';
5
+ export { useSiteAdmin, siteAdminQueryKey, } from './useSiteAdmin';
@@ -6,6 +6,16 @@
6
6
  */
7
7
  import type { NetworkClient } from '@sudobility/types';
8
8
  import type { FirebaseAuthNetworkClientOptions } from '../config/types';
9
+ /**
10
+ * Create a network client adapter that wraps a platform network
11
+ * with 401 retry and 403 logout handling.
12
+ *
13
+ * @param platformNetwork - The underlying network service to wrap (optional, defaults to getNetworkService())
14
+ * @param options - Optional callbacks for logout and token refresh failure
15
+ */
16
+ export declare function createFirebaseAuthNetworkClient(platformNetwork?: {
17
+ request: (url: string, options?: RequestInit) => Promise<Response>;
18
+ }, options?: FirebaseAuthNetworkClientOptions): NetworkClient;
9
19
  /**
10
20
  * Hook to get a Firebase Auth network client with automatic 401 retry and 403 logout.
11
21
  *
@@ -1 +1 @@
1
- {"version":3,"file":"useFirebaseAuthNetworkClient.d.ts","sourceRoot":"","sources":["../../src/hooks/useFirebaseAuthNetworkClient.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EACV,aAAa,EAId,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,iBAAiB,CAAC;AAyLxE;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,CAAC,EAAE,gCAAgC,GACzC,aAAa,CAEf"}
1
+ {"version":3,"file":"useFirebaseAuthNetworkClient.d.ts","sourceRoot":"","sources":["../../src/hooks/useFirebaseAuthNetworkClient.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EACV,aAAa,EAId,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,iBAAiB,CAAC;AAoCxE;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC7C,eAAe,CAAC,EAAE;IAChB,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CACpE,EACD,OAAO,CAAC,EAAE,gCAAgC,GACzC,aAAa,CA6If;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,CAAC,EAAE,gCAAgC,GACzC,aAAa,CAKf"}
@@ -41,11 +41,14 @@ async function logoutUser(onLogout) {
41
41
  }
42
42
  }
43
43
  /**
44
- * Create a network client adapter that wraps the platform network client
44
+ * Create a network client adapter that wraps a platform network
45
45
  * with 401 retry and 403 logout handling.
46
+ *
47
+ * @param platformNetwork - The underlying network service to wrap (optional, defaults to getNetworkService())
48
+ * @param options - Optional callbacks for logout and token refresh failure
46
49
  */
47
- function createFirebaseAuthNetworkClient(options) {
48
- const platformNetwork = getNetworkService();
50
+ export function createFirebaseAuthNetworkClient(platformNetwork, options) {
51
+ const network = platformNetwork ?? getNetworkService();
49
52
  const parseResponse = async (response) => {
50
53
  let data;
51
54
  const contentType = response.headers.get('content-type');
@@ -77,7 +80,7 @@ function createFirebaseAuthNetworkClient(options) {
77
80
  * - On 403: Log user out (no retry)
78
81
  */
79
82
  const executeWithRetry = async (url, requestInit) => {
80
- const response = await platformNetwork.request(url, requestInit);
83
+ const response = await network.request(url, requestInit);
81
84
  // On 401, get fresh token and retry once
82
85
  if (response.status === 401) {
83
86
  const freshToken = await getAuthToken(true);
@@ -86,7 +89,7 @@ function createFirebaseAuthNetworkClient(options) {
86
89
  ...requestInit.headers,
87
90
  Authorization: `Bearer ${freshToken}`,
88
91
  };
89
- const retryResponse = await platformNetwork.request(url, {
92
+ const retryResponse = await network.request(url, {
90
93
  ...requestInit,
91
94
  headers: retryHeaders,
92
95
  });
@@ -158,5 +161,5 @@ function createFirebaseAuthNetworkClient(options) {
158
161
  * @returns NetworkClient instance
159
162
  */
160
163
  export function useFirebaseAuthNetworkClient(options) {
161
- return useMemo(() => createFirebaseAuthNetworkClient(options), [options]);
164
+ return useMemo(() => createFirebaseAuthNetworkClient(undefined, options), [options]);
162
165
  }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @fileoverview Hook to check if the current user is a site admin.
3
+ *
4
+ * Fetches user info from the backend API and caches the result using TanStack Query.
5
+ * This provides a single source of truth for site admin status from the backend.
6
+ */
7
+ import type { NetworkClient, UserInfoResponse } from '@sudobility/types';
8
+ export type { UserInfoResponse } from '@sudobility/types';
9
+ /**
10
+ * Options for useSiteAdmin hook
11
+ */
12
+ export interface UseSiteAdminOptions {
13
+ /** Network client for making API requests */
14
+ networkClient: NetworkClient;
15
+ /** Base URL of the API (e.g., "https://api.example.com/api/v1") */
16
+ baseUrl: string;
17
+ /** Firebase user ID */
18
+ userId: string | undefined;
19
+ /** Firebase ID token for authentication */
20
+ token: string | undefined;
21
+ /** Cache time in milliseconds (default: 5 minutes) */
22
+ staleTime?: number;
23
+ /** Whether to enable the query (default: true when userId and token are provided) */
24
+ enabled?: boolean;
25
+ }
26
+ /**
27
+ * Return type for useSiteAdmin hook
28
+ */
29
+ export interface UseSiteAdminResult {
30
+ /** Whether the user is a site admin */
31
+ isSiteAdmin: boolean;
32
+ /** Full user info from the API */
33
+ userInfo: UserInfoResponse | null;
34
+ /** Whether the query is loading */
35
+ isLoading: boolean;
36
+ /** Whether the query has an error */
37
+ isError: boolean;
38
+ /** Error object if query failed */
39
+ error: Error | null;
40
+ /** Refetch the user info */
41
+ refetch: () => void;
42
+ }
43
+ /**
44
+ * Query key for site admin queries
45
+ */
46
+ export declare const siteAdminQueryKey: (userId: string) => readonly ["siteAdmin", string];
47
+ /**
48
+ * Hook to check if the current user is a site admin.
49
+ *
50
+ * Fetches user info from the backend /users/:userId endpoint and caches the result.
51
+ * The backend is the single source of truth for site admin status.
52
+ *
53
+ * @example
54
+ * ```tsx
55
+ * const { isSiteAdmin, isLoading } = useSiteAdmin({
56
+ * networkClient,
57
+ * baseUrl: 'https://api.example.com/api/v1',
58
+ * userId: user?.uid,
59
+ * token: idToken,
60
+ * });
61
+ *
62
+ * if (isLoading) return <Loading />;
63
+ * if (isSiteAdmin) return <AdminPanel />;
64
+ * ```
65
+ */
66
+ export declare function useSiteAdmin(options: UseSiteAdminOptions): UseSiteAdminResult;
67
+ //# sourceMappingURL=useSiteAdmin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSiteAdmin.d.ts","sourceRoot":"","sources":["../../src/hooks/useSiteAdmin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGzE,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAW1D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,6CAA6C;IAC7C,aAAa,EAAE,aAAa,CAAC;IAC7B,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,2CAA2C;IAC3C,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qFAAqF;IACrF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,uCAAuC;IACvC,WAAW,EAAE,OAAO,CAAC;IACrB,kCAAkC;IAClC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAClC,mCAAmC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,qCAAqC;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,mCAAmC;IACnC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,4BAA4B;IAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM,mCAChB,CAAC;AAEjC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,CAqD7E"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @fileoverview Hook to check if the current user is a site admin.
3
+ *
4
+ * Fetches user info from the backend API and caches the result using TanStack Query.
5
+ * This provides a single source of truth for site admin status from the backend.
6
+ */
7
+ import { useQuery } from '@tanstack/react-query';
8
+ /**
9
+ * Query key for site admin queries
10
+ */
11
+ export const siteAdminQueryKey = (userId) => ['siteAdmin', userId];
12
+ /**
13
+ * Hook to check if the current user is a site admin.
14
+ *
15
+ * Fetches user info from the backend /users/:userId endpoint and caches the result.
16
+ * The backend is the single source of truth for site admin status.
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * const { isSiteAdmin, isLoading } = useSiteAdmin({
21
+ * networkClient,
22
+ * baseUrl: 'https://api.example.com/api/v1',
23
+ * userId: user?.uid,
24
+ * token: idToken,
25
+ * });
26
+ *
27
+ * if (isLoading) return <Loading />;
28
+ * if (isSiteAdmin) return <AdminPanel />;
29
+ * ```
30
+ */
31
+ export function useSiteAdmin(options) {
32
+ const { networkClient, baseUrl, userId, token, staleTime = 5 * 60 * 1000, // 5 minutes default
33
+ enabled, } = options;
34
+ const isEnabled = enabled ?? (!!userId && !!token);
35
+ const query = useQuery({
36
+ queryKey: siteAdminQueryKey(userId ?? ''),
37
+ queryFn: async () => {
38
+ if (!userId || !token) {
39
+ return null;
40
+ }
41
+ const url = `${baseUrl}/users/${userId}`;
42
+ const response = await networkClient.get(url, {
43
+ headers: {
44
+ Authorization: `Bearer ${token}`,
45
+ 'Content-Type': 'application/json',
46
+ },
47
+ });
48
+ if (!response.ok || !response.data?.success || !response.data.data) {
49
+ // User not found or not authorized - not an admin
50
+ return null;
51
+ }
52
+ return response.data.data;
53
+ },
54
+ enabled: isEnabled,
55
+ staleTime,
56
+ // Retry once on failure (might be a transient network error)
57
+ retry: 1,
58
+ // Don't refetch on window focus for admin status
59
+ refetchOnWindowFocus: false,
60
+ });
61
+ return {
62
+ isSiteAdmin: query.data?.siteAdmin ?? false,
63
+ userInfo: query.data ?? null,
64
+ isLoading: query.isLoading,
65
+ isError: query.isError,
66
+ error: query.error,
67
+ refetch: query.refetch,
68
+ };
69
+ }
package/dist/index.d.ts CHANGED
@@ -9,6 +9,8 @@
9
9
  export { initializeFirebaseAuth, getFirebaseApp, getFirebaseAuth, getFirebaseConfig, isFirebaseConfigured, } from './config';
10
10
  export type { FirebaseConfig, FirebaseInitOptions, FirebaseInitResult, FirebaseAuthNetworkClientOptions, } from './config';
11
11
  export { useFirebaseAuthNetworkClient } from './hooks';
12
+ export { useSiteAdmin, siteAdminQueryKey, type UseSiteAdminOptions, type UseSiteAdminResult, type UserInfoResponse, } from './hooks';
12
13
  export { getFirebaseErrorMessage, getFirebaseErrorCode, formatFirebaseError, isFirebaseAuthError, } from './utils';
14
+ export { FirebaseAuthNetworkService, type FirebaseAuthNetworkServiceOptions, } from './network';
13
15
  export { parseAdminEmails, isAdminEmail, createAdminChecker } from './admin';
14
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAElB,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,gCAAgC,GACjC,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAC;AAGvD,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAElB,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,gCAAgC,GACjC,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAC;AAEvD,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,0BAA0B,EAC1B,KAAK,iCAAiC,GACvC,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -10,7 +10,10 @@
10
10
  export { initializeFirebaseAuth, getFirebaseApp, getFirebaseAuth, getFirebaseConfig, isFirebaseConfigured, } from './config';
11
11
  // Hooks
12
12
  export { useFirebaseAuthNetworkClient } from './hooks';
13
+ export { useSiteAdmin, siteAdminQueryKey, } from './hooks';
13
14
  // Utils
14
15
  export { getFirebaseErrorMessage, getFirebaseErrorCode, formatFirebaseError, isFirebaseAuthError, } from './utils';
16
+ // Network
17
+ export { FirebaseAuthNetworkService, } from './network';
15
18
  // Admin
16
19
  export { parseAdminEmails, isAdminEmail, createAdminChecker } from './admin';
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @fileoverview Firebase-aware network service with automatic token refresh and logout handling.
3
+ *
4
+ * Extends WebNetworkService to add:
5
+ * - On 401 (Unauthorized): Force refresh Firebase token and retry once
6
+ * - On 403 (Forbidden): Log the user out
7
+ */
8
+ import { WebNetworkService } from '@sudobility/di';
9
+ export interface FirebaseAuthNetworkServiceOptions {
10
+ /** Called when user is logged out due to 403 */
11
+ onLogout?: () => void;
12
+ /** Called when token refresh fails */
13
+ onTokenRefreshFailed?: (error: Error) => void;
14
+ }
15
+ /**
16
+ * Network service with Firebase authentication support.
17
+ * Automatically refreshes token on 401 and logs out on 403.
18
+ */
19
+ export declare class FirebaseAuthNetworkService extends WebNetworkService {
20
+ private options?;
21
+ constructor(options?: FirebaseAuthNetworkServiceOptions);
22
+ /**
23
+ * Override request to add 401 retry and 403 logout handling.
24
+ */
25
+ request(url: string, options?: RequestInit): Promise<Response>;
26
+ }
27
+ //# sourceMappingURL=FirebaseAuthNetworkService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FirebaseAuthNetworkService.d.ts","sourceRoot":"","sources":["../../src/network/FirebaseAuthNetworkService.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAInD,MAAM,WAAW,iCAAiC;IAChD,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,sCAAsC;IACtC,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC/C;AAiCD;;;GAGG;AACH,qBAAa,0BAA2B,SAAQ,iBAAiB;IAC/D,OAAO,CAAC,OAAO,CAAC,CAAoC;gBAExC,OAAO,CAAC,EAAE,iCAAiC;IAKvD;;OAEG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;CAkCzE"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * @fileoverview Firebase-aware network service with automatic token refresh and logout handling.
3
+ *
4
+ * Extends WebNetworkService to add:
5
+ * - On 401 (Unauthorized): Force refresh Firebase token and retry once
6
+ * - On 403 (Forbidden): Log the user out
7
+ */
8
+ import { WebNetworkService } from '@sudobility/di';
9
+ import { signOut } from 'firebase/auth';
10
+ import { getFirebaseAuth } from '../config/firebase-init';
11
+ /**
12
+ * Get a fresh Firebase ID token with force refresh.
13
+ * Returns empty string if not authenticated.
14
+ */
15
+ async function getAuthToken(forceRefresh = false) {
16
+ const auth = getFirebaseAuth();
17
+ const user = auth?.currentUser;
18
+ if (!user)
19
+ return '';
20
+ try {
21
+ return await user.getIdToken(forceRefresh);
22
+ }
23
+ catch (err) {
24
+ console.error('[FirebaseAuthNetworkService] Failed to get ID token:', err);
25
+ return '';
26
+ }
27
+ }
28
+ /**
29
+ * Log the user out via Firebase.
30
+ */
31
+ async function logoutUser(onLogout) {
32
+ const auth = getFirebaseAuth();
33
+ if (!auth)
34
+ return;
35
+ try {
36
+ await signOut(auth);
37
+ onLogout?.();
38
+ }
39
+ catch (err) {
40
+ console.error('[FirebaseAuthNetworkService] Failed to sign out:', err);
41
+ }
42
+ }
43
+ /**
44
+ * Network service with Firebase authentication support.
45
+ * Automatically refreshes token on 401 and logs out on 403.
46
+ */
47
+ export class FirebaseAuthNetworkService extends WebNetworkService {
48
+ constructor(options) {
49
+ super();
50
+ Object.defineProperty(this, "options", {
51
+ enumerable: true,
52
+ configurable: true,
53
+ writable: true,
54
+ value: void 0
55
+ });
56
+ this.options = options;
57
+ }
58
+ /**
59
+ * Override request to add 401 retry and 403 logout handling.
60
+ */
61
+ async request(url, options = {}) {
62
+ const response = await super.request(url, options);
63
+ // On 401, get fresh token and retry once
64
+ if (response.status === 401) {
65
+ const freshToken = await getAuthToken(true);
66
+ if (freshToken) {
67
+ const retryHeaders = {
68
+ ...options.headers,
69
+ Authorization: `Bearer ${freshToken}`,
70
+ };
71
+ return super.request(url, {
72
+ ...options,
73
+ headers: retryHeaders,
74
+ });
75
+ }
76
+ else {
77
+ // Token refresh failed
78
+ this.options?.onTokenRefreshFailed?.(new Error('Failed to refresh token'));
79
+ }
80
+ }
81
+ // On 403, log the user out
82
+ if (response.status === 403) {
83
+ console.warn('[FirebaseAuthNetworkService] 403 Forbidden - logging user out');
84
+ await logoutUser(this.options?.onLogout);
85
+ // Return the original response so the UI can handle it
86
+ }
87
+ return response;
88
+ }
89
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @fileoverview Network exports
3
+ */
4
+ export { FirebaseAuthNetworkService, type FirebaseAuthNetworkServiceOptions, } from './FirebaseAuthNetworkService';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/network/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,0BAA0B,EAC1B,KAAK,iCAAiC,GACvC,MAAM,8BAA8B,CAAC"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @fileoverview Network exports
3
+ */
4
+ export { FirebaseAuthNetworkService, } from './FirebaseAuthNetworkService';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sudobility/auth_lib",
3
- "version": "0.0.12",
3
+ "version": "0.0.16",
4
4
  "description": "Firebase authentication utilities with token refresh handling",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -31,10 +31,13 @@
31
31
  "peerDependencies": {
32
32
  "react": "^19.2.3",
33
33
  "firebase": "^12.7.0",
34
- "@sudobility/di": "^1.5.20",
35
- "@sudobility/types": "^1.9.46"
34
+ "@sudobility/di": "^1.5.22",
35
+ "@sudobility/types": "^1.9.48",
36
+ "@tanstack/react-query": "^5.0.0"
36
37
  },
37
38
  "devDependencies": {
39
+ "@sudobility/types": "^1.9.48",
40
+ "@tanstack/react-query": "^5.80.7",
38
41
  "vitest": "^4.0.4",
39
42
  "@eslint/js": "^9.0.0",
40
43
  "@types/bun": "^1.2.8",
@@ -1,56 +0,0 @@
1
- /**
2
- * @fileoverview Admin email whitelist utilities
3
- *
4
- * Provides functions for parsing and checking admin email whitelists,
5
- * typically used for bypassing rate limits and subscription checks.
6
- */
7
- /**
8
- * Parse admin emails from environment variable or string
9
- *
10
- * @param input - Comma-separated string of admin emails (e.g., "admin@example.com, other@example.com")
11
- * @returns Array of normalized (lowercase, trimmed) email addresses
12
- *
13
- * @example
14
- * ```typescript
15
- * const adminEmails = parseAdminEmails(process.env.ADMIN_EMAILS || "");
16
- * // Returns: ["admin@example.com", "other@example.com"]
17
- * ```
18
- */
19
- export declare function parseAdminEmails(input: string | undefined | null): string[];
20
- /**
21
- * Check if an email is in the admin whitelist
22
- *
23
- * @param email - Email address to check
24
- * @param adminEmails - Array of admin emails (from parseAdminEmails)
25
- * @returns True if the email is an admin
26
- *
27
- * @example
28
- * ```typescript
29
- * const adminEmails = parseAdminEmails(process.env.ADMIN_EMAILS || "");
30
- * if (isAdminEmail(user.email, adminEmails)) {
31
- * // Skip rate limiting, subscription checks, etc.
32
- * }
33
- * ```
34
- */
35
- export declare function isAdminEmail(email: string | undefined | null, adminEmails: string[]): boolean;
36
- /**
37
- * Create a cached admin email checker
38
- *
39
- * Parses the admin emails once and returns a checker function.
40
- * Useful for middleware where you don't want to re-parse on every request.
41
- *
42
- * @param input - Comma-separated string of admin emails
43
- * @returns Function that checks if an email is an admin
44
- *
45
- * @example
46
- * ```typescript
47
- * const isAdmin = createAdminChecker(process.env.ADMIN_EMAILS);
48
- *
49
- * // In middleware:
50
- * if (isAdmin(user.email)) {
51
- * // Bypass checks
52
- * }
53
- * ```
54
- */
55
- export declare function createAdminChecker(input: string | undefined | null): (email: string | undefined | null) => boolean;
56
- //# sourceMappingURL=admin-emails.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"admin-emails.d.ts","sourceRoot":"","sources":["../../src/admin/admin-emails.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,EAAE,CAS3E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAChC,WAAW,EAAE,MAAM,EAAE,GACpB,OAAO,CAMT;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAC/B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,KAAK,OAAO,CAM/C"}
@@ -1,73 +0,0 @@
1
- /**
2
- * @fileoverview Admin email whitelist utilities
3
- *
4
- * Provides functions for parsing and checking admin email whitelists,
5
- * typically used for bypassing rate limits and subscription checks.
6
- */
7
- /**
8
- * Parse admin emails from environment variable or string
9
- *
10
- * @param input - Comma-separated string of admin emails (e.g., "admin@example.com, other@example.com")
11
- * @returns Array of normalized (lowercase, trimmed) email addresses
12
- *
13
- * @example
14
- * ```typescript
15
- * const adminEmails = parseAdminEmails(process.env.ADMIN_EMAILS || "");
16
- * // Returns: ["admin@example.com", "other@example.com"]
17
- * ```
18
- */
19
- export function parseAdminEmails(input) {
20
- if (!input) {
21
- return [];
22
- }
23
- return input
24
- .split(',')
25
- .map(email => email.trim().toLowerCase())
26
- .filter(email => email.length > 0 && email.includes('@'));
27
- }
28
- /**
29
- * Check if an email is in the admin whitelist
30
- *
31
- * @param email - Email address to check
32
- * @param adminEmails - Array of admin emails (from parseAdminEmails)
33
- * @returns True if the email is an admin
34
- *
35
- * @example
36
- * ```typescript
37
- * const adminEmails = parseAdminEmails(process.env.ADMIN_EMAILS || "");
38
- * if (isAdminEmail(user.email, adminEmails)) {
39
- * // Skip rate limiting, subscription checks, etc.
40
- * }
41
- * ```
42
- */
43
- export function isAdminEmail(email, adminEmails) {
44
- if (!email) {
45
- return false;
46
- }
47
- return adminEmails.includes(email.toLowerCase());
48
- }
49
- /**
50
- * Create a cached admin email checker
51
- *
52
- * Parses the admin emails once and returns a checker function.
53
- * Useful for middleware where you don't want to re-parse on every request.
54
- *
55
- * @param input - Comma-separated string of admin emails
56
- * @returns Function that checks if an email is an admin
57
- *
58
- * @example
59
- * ```typescript
60
- * const isAdmin = createAdminChecker(process.env.ADMIN_EMAILS);
61
- *
62
- * // In middleware:
63
- * if (isAdmin(user.email)) {
64
- * // Bypass checks
65
- * }
66
- * ```
67
- */
68
- export function createAdminChecker(input) {
69
- const adminEmails = parseAdminEmails(input);
70
- return (email) => {
71
- return isAdminEmail(email, adminEmails);
72
- };
73
- }