@rebasepro/auth 0.1.2 → 0.2.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.
package/dist/types.d.ts CHANGED
@@ -4,14 +4,15 @@ import { AuthController, Role, User } from "@rebasepro/types";
4
4
  * with additional methods for email/password and Google login
5
5
  */
6
6
  export type RebaseAuthController = AuthController & {
7
- /** Login with Google — accepts legacy (token, tokenType) or code-flow payload */
8
- googleLogin: {
9
- (token: string, tokenType?: "idToken" | "accessToken"): Promise<void>;
10
- (payload: {
11
- code: string;
12
- redirectUri: string;
13
- }): Promise<void>;
14
- };
7
+ /** Login with Google — accepts an ID token, access token, or authorization code payload */
8
+ googleLogin: (payload: {
9
+ idToken: string;
10
+ } | {
11
+ accessToken: string;
12
+ } | {
13
+ code: string;
14
+ redirectUri: string;
15
+ }) => Promise<void>;
15
16
  /** Generic OAuth login — works with any provider. Posts payload to /auth/{providerId}. */
16
17
  oauthLogin: (providerId: string, payload: Record<string, unknown>) => Promise<void>;
17
18
  /** Login with email and password */
@@ -44,6 +45,10 @@ export type RebaseAuthController = AuthController & {
44
45
  revokeAllSessions: () => Promise<void>;
45
46
  /** Get internal API URL */
46
47
  getApiUrl?: () => string | undefined;
48
+ /** Clear the current auth provider error */
49
+ clearError: () => void;
50
+ /** Set or clear the auth provider error */
51
+ setAuthProviderError: (error: Error | null) => void;
47
52
  };
48
53
  /**
49
54
  * Props for useRebaseAuthController hook
@@ -79,6 +84,7 @@ export interface UserInfo {
79
84
  photoURL?: string | null;
80
85
  emailVerified?: boolean;
81
86
  roles?: string[];
87
+ metadata?: Record<string, unknown>;
82
88
  }
83
89
  /**
84
90
  * Auth response from backend login/register endpoints
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/auth",
3
3
  "type": "module",
4
- "version": "0.1.2",
4
+ "version": "0.2.3",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -11,9 +11,9 @@
11
11
  "types": "./dist/index.d.ts",
12
12
  "source": "src/index.ts",
13
13
  "dependencies": {
14
- "@rebasepro/types": "0.1.2",
15
- "@rebasepro/ui": "0.1.2",
16
- "@rebasepro/core": "0.1.2"
14
+ "@rebasepro/core": "0.2.3",
15
+ "@rebasepro/types": "0.2.3",
16
+ "@rebasepro/ui": "0.2.3"
17
17
  },
18
18
  "peerDependencies": {
19
19
  "react": ">=19.0.0",
@@ -22,24 +22,30 @@
22
22
  "exports": {
23
23
  ".": {
24
24
  "types": "./dist/index.d.ts",
25
- "development": "./src/index.ts",
25
+ "development": "./dist/index.es.js",
26
26
  "import": "./dist/index.es.js",
27
27
  "require": "./dist/index.umd.js"
28
28
  },
29
29
  "./package.json": "./package.json"
30
30
  },
31
31
  "devDependencies": {
32
- "@types/node": "^20.19.17",
33
- "@types/react": "^19.0.8",
34
- "@types/react-dom": "^19.0.3",
32
+ "@types/node": "^20.19.41",
33
+ "@types/react": "^19.2.15",
34
+ "@types/react-dom": "^19.2.3",
35
+ "@vitejs/plugin-react": "^4.7.0",
35
36
  "typescript": "^5.9.3",
36
- "vite": "^7.2.4"
37
+ "vite": "^7.3.3"
37
38
  },
38
39
  "files": [
39
40
  "dist",
40
41
  "src"
41
42
  ],
42
43
  "gitHead": "d935eefa5aa8d1009a2398cfac2c1e4ee9aeb6b6",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/rebasepro/rebase.git",
47
+ "directory": "packages/auth"
48
+ },
43
49
  "scripts": {
44
50
  "dev": "vite",
45
51
  "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json",
package/src/api.ts CHANGED
@@ -113,23 +113,16 @@ export type GoogleLoginPayload =
113
113
  /**
114
114
  * Login with Google.
115
115
  *
116
- * Overload 1 (legacy): `googleLogin("token", "idToken" | "accessToken")`
117
- * Overload 2 (code flow): `googleLogin({ code, redirectUri })`
116
+ * Accepts one of:
117
+ * - `{ idToken }` — ID-token flow (One Tap / Sign In button)
118
+ * - `{ accessToken }` — Access-token flow (popup)
119
+ * - `{ code, redirectUri }` — Authorization code flow (most secure)
118
120
  */
119
- export async function googleLogin(payload: GoogleLoginPayload): Promise<AuthResponse>;
120
- export async function googleLogin(token: string, tokenType?: "idToken" | "accessToken"): Promise<AuthResponse>;
121
- export async function googleLogin(
122
- tokenOrPayload: string | GoogleLoginPayload,
123
- tokenType: "idToken" | "accessToken" = "idToken"
124
- ): Promise<AuthResponse> {
125
- const body = typeof tokenOrPayload === "string"
126
- ? { [tokenType]: tokenOrPayload }
127
- : tokenOrPayload;
128
-
121
+ export async function googleLogin(payload: GoogleLoginPayload): Promise<AuthResponse> {
129
122
  const response = await fetchWithHandling(`${baseApiUrl}/api/auth/google`, {
130
123
  method: "POST",
131
124
  headers: { "Content-Type": "application/json" },
132
- body: JSON.stringify(body)
125
+ body: JSON.stringify(payload)
133
126
  });
134
127
 
135
128
  return handleResponse<AuthResponse>(response);
@@ -167,15 +160,12 @@ export async function oauthLogin(providerId: string, payload: Record<string, unk
167
160
  * Refresh access token using refresh token
168
161
  */
169
162
  export async function refreshAccessToken(refreshToken: string): Promise<RefreshResponse> {
170
- console.log("[AUTH-API] Calling refresh endpoint...");
171
-
172
163
  const response = await fetchWithHandling(`${baseApiUrl}/api/auth/refresh`, {
173
164
  method: "POST",
174
165
  headers: { "Content-Type": "application/json" },
175
166
  body: JSON.stringify({ refreshToken })
176
167
  });
177
168
 
178
- console.log("[AUTH-API] Refresh response status:", response.status);
179
169
  return handleResponse<RefreshResponse>(response);
180
170
  }
181
171
 
@@ -359,14 +349,10 @@ export interface AuthConfigResponse {
359
349
  needsSetup: boolean;
360
350
  /** Whether new user registration is enabled */
361
351
  registrationEnabled: boolean;
362
- /** Whether Google OAuth is configured */
363
- googleEnabled: boolean;
364
- /** Whether LinkedIn OAuth is configured */
365
- linkedinEnabled: boolean;
366
352
  /** Whether email service is configured */
367
353
  emailServiceEnabled: boolean;
368
354
  /** Complete list of enabled OAuth provider IDs (e.g. ["google", "github", "discord"]) */
369
- enabledProviders?: string[];
355
+ enabledProviders: string[];
370
356
  }
371
357
 
372
358
  /**
@@ -375,14 +361,26 @@ export interface AuthConfigResponse {
375
361
  */
376
362
  let authConfigInflight: Promise<AuthConfigResponse> | null = null;
377
363
 
364
+ /**
365
+ * Cached result of the last successful `fetchAuthConfig` call.
366
+ * Auth config is static for the lifetime of the app session, so
367
+ * repeat calls (e.g. from effect re-runs) return instantly.
368
+ */
369
+ let authConfigCached: AuthConfigResponse | null = null;
370
+
378
371
  /**
379
372
  * Fetch auth configuration / status from the backend
380
373
  * This is an unauthenticated endpoint used to detect bootstrap mode.
381
374
  *
375
+ * Results are cached for the session lifetime.
382
376
  * Concurrent calls are deduplicated: only one network request is made
383
377
  * and all callers share the same promise.
384
378
  */
385
379
  export async function fetchAuthConfig(): Promise<AuthConfigResponse> {
380
+ if (authConfigCached) {
381
+ return authConfigCached;
382
+ }
383
+
386
384
  if (authConfigInflight) {
387
385
  return authConfigInflight;
388
386
  }
@@ -396,11 +394,22 @@ export async function fetchAuthConfig(): Promise<AuthConfigResponse> {
396
394
  })();
397
395
 
398
396
  try {
399
- return await authConfigInflight;
397
+ const result = await authConfigInflight;
398
+ authConfigCached = result;
399
+ return result;
400
400
  } finally {
401
401
  authConfigInflight = null;
402
402
  }
403
403
  }
404
404
 
405
+ /**
406
+ * Clear the cached auth config (e.g. on logout or for testing).
407
+ */
408
+ export function clearAuthConfigCache(): void {
409
+ authConfigCached = null;
410
+ authConfigInflight = null;
411
+ }
412
+
405
413
  export { AuthApiError };
406
414
 
415
+
@@ -284,6 +284,15 @@ export function useBackendUserManagement(config: BackendUserManagementConfig): U
284
284
  return;
285
285
  }
286
286
 
287
+ // Skip admin API calls for non-admin users — they'd get 403 anyway.
288
+ // This avoids a spurious warning in backend logs on every non-admin login.
289
+ const userRoles = currentUser.roles ?? [];
290
+ const isUserAdmin = userRoles.some(r => r === "admin" || r === "schema-admin");
291
+ if (!isUserAdmin) {
292
+ setLoading(false);
293
+ return;
294
+ }
295
+
287
296
  // Skip refetch if we already loaded data for this same UID
288
297
  // (e.g. React StrictMode unmounts and re-mounts with the same user).
289
298
  if (lastLoadedUidRef.current === currentUser.uid) {
@@ -548,7 +557,7 @@ export function useBackendUserManagement(config: BackendUserManagementConfig): U
548
557
  saveRole,
549
558
  deleteRole,
550
559
  isAdmin,
551
- allowDefaultRolesCreation: true,
560
+ allowDefaultRolesCreation: isAdmin,
552
561
  includeCollectionConfigPermissions: true,
553
562
  defineRolesFor,
554
563
  getUser,
@@ -25,7 +25,8 @@ function convertToUser(userInfo: UserInfo): User {
25
25
  photoURL: userInfo.photoURL || null,
26
26
  providerId: "custom",
27
27
  isAnonymous: false,
28
- roles: userInfo.roles || []
28
+ roles: userInfo.roles || [],
29
+ metadata: userInfo.metadata
29
30
  };
30
31
  }
31
32
 
@@ -42,11 +43,8 @@ interface StoredAuthData {
42
43
  */
43
44
  function saveAuthToStorage(tokens: AuthTokens, user: UserInfo): void {
44
45
  try {
45
- const data: StoredAuthData = { tokens,
46
- user };
46
+ const data: StoredAuthData = { tokens, user };
47
47
  localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
48
- const expiryDate = new Date(tokens.accessTokenExpiresAt);
49
- const expiryStr = Number.isFinite(tokens.accessTokenExpiresAt) ? expiryDate.toISOString() : "invalid";
50
48
  } catch (e) { /* ignore */ }
51
49
  }
52
50
 
@@ -122,6 +120,10 @@ export function useRebaseAuthController(
122
120
  }
123
121
  }, [client, apiUrl]);
124
122
 
123
+ const clearError = useCallback(() => {
124
+ setAuthProviderError(null);
125
+ }, []);
126
+
125
127
  // Clear session and sign out
126
128
  const clearSessionAndSignOut = useCallback(() => {
127
129
  tokensRef.current = null;
@@ -177,7 +179,6 @@ export function useRebaseAuthController(
177
179
  saveAuthToStorage(newTokens, latestStoredData.user);
178
180
  }
179
181
 
180
- const newExpiryStr = Number.isFinite(newTokens.accessTokenExpiresAt) ? new Date(newTokens.accessTokenExpiresAt).toISOString() : "invalid";
181
182
  return newTokens;
182
183
  } catch (error: unknown) {
183
184
 
@@ -306,7 +307,6 @@ export function useRebaseAuthController(
306
307
 
307
308
  // Handle successful authentication
308
309
  const handleAuthSuccess = useCallback(async (userInfo: UserInfo, tokens: AuthTokens) => {
309
- console.log("[Auth] handleAuthSuccess called, user:", userInfo.email, "uid:", userInfo.uid);
310
310
  tokensRef.current = tokens;
311
311
  let convertedUser = convertToUser(userInfo);
312
312
 
@@ -314,21 +314,18 @@ export function useRebaseAuthController(
314
314
  if (defineRolesFor) {
315
315
  const customRoles = await defineRolesFor(convertedUser);
316
316
  if (customRoles) {
317
- convertedUser = { ...convertedUser,
318
- roles: customRoles.map(r => r.id) };
317
+ convertedUser = { ...convertedUser, roles: customRoles.map(r => r.id) };
319
318
  }
320
319
  }
321
320
 
322
321
  // Save to localStorage for persistence
323
322
  saveAuthToStorage(tokens, userInfo);
324
323
 
325
- console.log("[Auth] Calling setUser, roles:", convertedUser.roles);
326
324
  setUser(convertedUser);
327
325
  setAuthError(null);
328
326
  setAuthProviderError(null);
329
327
  setLoginSkipped(false);
330
328
  scheduleTokenRefresh(tokens);
331
- console.log("[Auth] handleAuthSuccess completed");
332
329
  }, [scheduleTokenRefresh, defineRolesFor]);
333
330
 
334
331
  // Email/password login
@@ -363,18 +360,15 @@ roles: customRoles.map(r => r.id) };
363
360
  }
364
361
  }, [handleAuthSuccess]);
365
362
 
366
- // Google login — supports legacy (token, tokenType) and code-flow payload
363
+ // Google login — accepts payload object with code flow or token
367
364
  const googleLogin = useCallback(async (
368
- tokenOrPayload: string | { code: string; redirectUri: string },
369
- tokenType?: "idToken" | "accessToken"
365
+ payload: { code: string; redirectUri: string } | { idToken: string } | { accessToken: string }
370
366
  ) => {
371
367
  setAuthLoading(true);
372
368
  setAuthProviderError(null);
373
369
 
374
370
  try {
375
- const response = typeof tokenOrPayload === "string"
376
- ? await authApi.googleLogin(tokenOrPayload, tokenType ?? "idToken")
377
- : await authApi.googleLogin(tokenOrPayload);
371
+ const response = await authApi.googleLogin(payload);
378
372
  await handleAuthSuccess(response.user, response.tokens);
379
373
  } catch (error: unknown) {
380
374
  setAuthProviderError(error as Error);
@@ -742,6 +736,8 @@ roles: customRoles.map(r => r.id) };
742
736
  fetchSessions,
743
737
  revokeSession,
744
738
  revokeAllSessions,
739
+ clearError,
740
+ setAuthProviderError,
745
741
  extra,
746
742
  setExtra,
747
743
  capabilities: {
package/src/index.ts CHANGED
@@ -21,13 +21,6 @@ export { useRebaseAuthController } from "./hooks/useRebaseAuthController";
21
21
  export { useBackendUserManagement } from "./hooks/useBackendUserManagement";
22
22
  export type { BackendUserManagementConfig, UserManagement } from "./hooks/useBackendUserManagement";
23
23
 
24
- // Legacy re-exports for backward compatibility
25
- // The UI components have moved to @rebasepro/core
26
- export { RebaseLoginView } from "./components/RebaseLoginView";
27
- export type { RebaseLoginViewProps } from "./components/RebaseLoginView";
28
- export { RebaseAuth } from "./components/RebaseAuth";
29
- export { createUserManagementAdminViews, UsersView, RolesView } from "./components/AdminViews";
30
-
31
24
  // API utilities
32
- export { setApiUrl, getApiUrl, fetchAuthConfig, AuthApiError } from "./api";
25
+ export { setApiUrl, getApiUrl, fetchAuthConfig, clearAuthConfigCache, AuthApiError } from "./api";
33
26
  export type { AuthConfigResponse } from "./api";
package/src/types.ts CHANGED
@@ -5,11 +5,8 @@ import { AuthController, Role, User } from "@rebasepro/types";
5
5
  * with additional methods for email/password and Google login
6
6
  */
7
7
  export type RebaseAuthController = AuthController & {
8
- /** Login with Google — accepts legacy (token, tokenType) or code-flow payload */
9
- googleLogin: {
10
- (token: string, tokenType?: "idToken" | "accessToken"): Promise<void>;
11
- (payload: { code: string; redirectUri: string }): Promise<void>;
12
- };
8
+ /** Login with Google — accepts an ID token, access token, or authorization code payload */
9
+ googleLogin: (payload: { idToken: string } | { accessToken: string } | { code: string; redirectUri: string }) => Promise<void>;
13
10
  /** Generic OAuth login — works with any provider. Posts payload to /auth/{providerId}. */
14
11
  oauthLogin: (providerId: string, payload: Record<string, unknown>) => Promise<void>;
15
12
  /** Login with email and password */
@@ -42,6 +39,10 @@ export type RebaseAuthController = AuthController & {
42
39
  revokeAllSessions: () => Promise<void>;
43
40
  /** Get internal API URL */
44
41
  getApiUrl?: () => string | undefined;
42
+ /** Clear the current auth provider error */
43
+ clearError: () => void;
44
+ /** Set or clear the auth provider error */
45
+ setAuthProviderError: (error: Error | null) => void;
45
46
  }
46
47
 
47
48
  /**
@@ -80,6 +81,7 @@ export interface UserInfo {
80
81
  photoURL?: string | null;
81
82
  emailVerified?: boolean;
82
83
  roles?: string[];
84
+ metadata?: Record<string, unknown>;
83
85
  }
84
86
 
85
87
  /**
@@ -1,22 +0,0 @@
1
- import { AppView, EntityCollection } from "@rebasepro/types";
2
- import { UserManagement } from "../hooks/useBackendUserManagement";
3
- interface AdminViewsProps {
4
- userManagement: UserManagement;
5
- apiUrl: string;
6
- getAuthToken: () => Promise<string>;
7
- collections?: EntityCollection[];
8
- }
9
- /**
10
- * Create admin views for user and role management
11
- */
12
- export declare function createUserManagementAdminViews({ userManagement, apiUrl, getAuthToken, collections }: AdminViewsProps): AppView[];
13
- export declare function UsersView({ userManagement, apiUrl, getAuthToken }: {
14
- userManagement: UserManagement;
15
- apiUrl: string;
16
- getAuthToken: () => Promise<string>;
17
- }): import("react/jsx-runtime").JSX.Element;
18
- export declare function RolesView({ userManagement, collections }: {
19
- userManagement: UserManagement;
20
- collections?: EntityCollection[];
21
- }): import("react/jsx-runtime").JSX.Element;
22
- export {};
@@ -1,6 +0,0 @@
1
- import type { RebaseAuthConfig } from "@rebasepro/types";
2
- /**
3
- * Declarative component to configure authentication in Rebase.
4
- * Renders nothing — purely registers config into the RebaseRegistry.
5
- */
6
- export declare function RebaseAuth({ loginView }: RebaseAuthConfig): null;
@@ -1,73 +0,0 @@
1
- import { ReactNode } from "react";
2
- import { RebaseAuthController } from "../types";
3
- /**
4
- * Props for RebaseLoginView
5
- */
6
- export interface RebaseLoginViewProps {
7
- /**
8
- * Auth controller from useRebaseAuthController
9
- */
10
- authController: RebaseAuthController;
11
- /**
12
- * Path to the logo displayed in the login screen
13
- */
14
- logo?: string;
15
- /**
16
- * Enable the skip login button
17
- */
18
- allowSkipLogin?: boolean;
19
- /**
20
- * Disable the login buttons
21
- */
22
- disabled?: boolean;
23
- /**
24
- * Prevent users from creating new accounts
25
- */
26
- disableSignupScreen?: boolean;
27
- /**
28
- * Display this component when no user is found
29
- */
30
- noUserComponent?: ReactNode;
31
- /**
32
- * Display this component below the sign-in buttons
33
- */
34
- additionalComponent?: ReactNode;
35
- /**
36
- * Error message when user is not allowed access
37
- */
38
- notAllowedError?: string | Error;
39
- /**
40
- * Enable Google login button (requires googleClientId in hook)
41
- */
42
- googleEnabled?: boolean;
43
- /**
44
- * Google client ID for OAuth
45
- */
46
- googleClientId?: string;
47
- }
48
- /**
49
- * Login view component for custom JWT authentication
50
- */
51
- export declare function RebaseLoginView({ logo, authController, noUserComponent, disableSignupScreen, disabled, notAllowedError, googleEnabled, googleClientId }: RebaseLoginViewProps): import("react/jsx-runtime").JSX.Element;
52
- /** Google Identity Services SDK — injected by the GIS <script> tag. */
53
- declare global {
54
- interface Window {
55
- google?: {
56
- accounts: {
57
- oauth2: {
58
- initCodeClient(config: {
59
- client_id: string;
60
- scope: string;
61
- ux_mode: "popup" | "redirect";
62
- callback: (response: {
63
- code?: string;
64
- error?: string;
65
- }) => void;
66
- }): {
67
- requestCode(): void;
68
- };
69
- };
70
- };
71
- };
72
- }
73
- }