spry-apps-dropdown 2.0.3 → 3.0.0
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/README.md +92 -2
- package/dist/AccountList.d.ts +21 -0
- package/dist/ProfileMenu.d.ts +5 -2
- package/dist/ProfileMenuConnected.d.ts +3 -2
- package/dist/SpryAuthProvider.d.ts +15 -0
- package/dist/TopBar.d.ts +2 -1
- package/dist/accountStorage.d.ts +104 -0
- package/dist/constants.d.ts +44 -0
- package/dist/index.cjs +16 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.js +2965 -1627
- package/dist/index.js.map +1 -1
- package/dist/keycloakLogout.d.ts +58 -0
- package/dist/syncBridge.d.ts +8 -0
- package/dist/tokenUtils.d.ts +58 -0
- package/dist/types.d.ts +56 -0
- package/dist/useAccountManager.d.ts +28 -0
- package/dist/useSpryAccountManager.d.ts +8 -0
- package/dist/useSpryAuth.d.ts +1 -0
- package/dist/userManagerPool.d.ts +84 -0
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -1,4 +1,57 @@
|
|
|
1
1
|
# spry-apps-dropdown
|
|
2
|
+
## Integration Guide
|
|
3
|
+
|
|
4
|
+
### 1. OIDC/Keycloak Setup
|
|
5
|
+
|
|
6
|
+
This package requires OIDC authentication. Use `react-oidc-context` or `@react-keycloak/web` for best results.
|
|
7
|
+
|
|
8
|
+
**Example OIDC config:**
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
const oidcConfig = {
|
|
12
|
+
authority: 'https://auth.sprylogin.com/realms/sprylogin',
|
|
13
|
+
client_id: 'my-app-client',
|
|
14
|
+
redirect_uri: window.location.origin,
|
|
15
|
+
post_logout_redirect_uri: window.location.origin,
|
|
16
|
+
onSigninCallback: () => {
|
|
17
|
+
window.history.replaceState({}, document.title, window.location.pathname)
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 2. Multi-Account Management
|
|
23
|
+
|
|
24
|
+
The package supports multi-account switching, add-account, and soft logout. All account state is stored in localStorage for cross-app/iframe sync.
|
|
25
|
+
|
|
26
|
+
**Syncing across apps/iframe:**
|
|
27
|
+
- Use the same localStorage key (`spry_accounts`) in all apps.
|
|
28
|
+
- When an account is added, removed, or switched, update localStorage and reload state in all apps.
|
|
29
|
+
- For iframe/bridge integration, use a shared localStorage or a custom syncBridge client.
|
|
30
|
+
|
|
31
|
+
### 3. Add Another Account Flow
|
|
32
|
+
|
|
33
|
+
When adding another account:
|
|
34
|
+
- A flag (`spry_add_account_pending`) is set in localStorage.
|
|
35
|
+
- After soft logout, the app checks this flag and triggers OIDC login for the new account.
|
|
36
|
+
- On callback, the new account is added and set as active.
|
|
37
|
+
|
|
38
|
+
### 4. Cross-App Sync
|
|
39
|
+
|
|
40
|
+
To sync active account and account removal across apps:
|
|
41
|
+
- Listen for changes to `spry_accounts` in localStorage (using the `storage` event or polling).
|
|
42
|
+
- When the accounts state changes, update your app's state and UI.
|
|
43
|
+
- If using an iframe bridge, ensure the bridge propagates account changes to all connected apps.
|
|
44
|
+
|
|
45
|
+
### 5. Troubleshooting
|
|
46
|
+
|
|
47
|
+
- If new accounts are not added after login, ensure your OIDC provider updates the user context before the callback runs.
|
|
48
|
+
- If cross-app sync is not working, check that all apps use the same localStorage key and listen for changes.
|
|
49
|
+
- For CORS or redirect issues, verify your OIDC config and Keycloak setup.
|
|
50
|
+
|
|
51
|
+
### 6. Example Integration
|
|
52
|
+
|
|
53
|
+
See the Quick Start and Usage sections below for full code examples.
|
|
54
|
+
|
|
2
55
|
|
|
3
56
|
A React component library for displaying Spry apps dropdown and profile menu with dynamic API integration. Features a Google-style UI with beautiful animations.
|
|
4
57
|
|
|
@@ -17,10 +70,47 @@ A React component library for displaying Spry apps dropdown and profile menu wit
|
|
|
17
70
|
## Installation
|
|
18
71
|
|
|
19
72
|
```bash
|
|
20
|
-
npm install spry-apps-dropdown
|
|
73
|
+
npm install spry-apps-dropdown react-oidc-context oidc-client-ts
|
|
21
74
|
```
|
|
22
75
|
|
|
23
|
-
|
|
76
|
+
This package expects `react`, `react-dom`, `react-oidc-context`, and `oidc-client-ts` to be provided by the consuming app.
|
|
77
|
+
|
|
78
|
+
## React OIDC Quick Start (Lowest Effort)
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import React from 'react'
|
|
82
|
+
import ReactDOM from 'react-dom/client'
|
|
83
|
+
import { SpryAuthProvider, TopBar, useSpryAccountManager } from 'spry-apps-dropdown'
|
|
84
|
+
|
|
85
|
+
const oidcConfig = {
|
|
86
|
+
authority: 'https://auth.sprylogin.com/realms/sprylogin',
|
|
87
|
+
client_id: 'my-app-client',
|
|
88
|
+
redirect_uri: window.location.origin,
|
|
89
|
+
post_logout_redirect_uri: window.location.origin,
|
|
90
|
+
onSigninCallback: () => {
|
|
91
|
+
window.history.replaceState({}, document.title, window.location.pathname)
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function App() {
|
|
96
|
+
const accountManager = useSpryAccountManager()
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<TopBar
|
|
100
|
+
apiUrl="https://your-api.com"
|
|
101
|
+
accountManager={accountManager}
|
|
102
|
+
/>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
107
|
+
<React.StrictMode>
|
|
108
|
+
<SpryAuthProvider config={oidcConfig}>
|
|
109
|
+
<App />
|
|
110
|
+
</SpryAuthProvider>
|
|
111
|
+
</React.StrictMode>
|
|
112
|
+
)
|
|
113
|
+
```
|
|
24
114
|
|
|
25
115
|
## Usage
|
|
26
116
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AccountList Component
|
|
3
|
+
* Renders a dropdown list of stored accounts with ability to switch or remove
|
|
4
|
+
* Shows stale account badges for expired refresh tokens
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import type { StoredAccount } from './types';
|
|
8
|
+
export interface AccountListProps {
|
|
9
|
+
accounts: StoredAccount[];
|
|
10
|
+
activeAccountId: string | null;
|
|
11
|
+
isLoading?: boolean;
|
|
12
|
+
onSwitchAccount: (accountId: string) => Promise<void>;
|
|
13
|
+
onAddAccount: () => Promise<void>;
|
|
14
|
+
onRemoveAccount: (accountId: string) => Promise<void>;
|
|
15
|
+
onRefreshAccount: (accountId: string) => Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* AccountList component
|
|
19
|
+
* Renders all stored accounts with switching, adding, and removal UI
|
|
20
|
+
*/
|
|
21
|
+
export declare const AccountList: React.ForwardRefExoticComponent<AccountListProps & React.RefAttributes<HTMLDivElement>>;
|
package/dist/ProfileMenu.d.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
import type { ProfileMenuProps } from './types';
|
|
2
|
-
export
|
|
1
|
+
import type { ProfileMenuProps, UseAccountManagerReturn } from './types';
|
|
2
|
+
export interface ProfileMenuWithAccountsProps extends ProfileMenuProps {
|
|
3
|
+
accountManager?: UseAccountManagerReturn;
|
|
4
|
+
}
|
|
5
|
+
export declare function ProfileMenu({ profile, isLoading, onSignOut, onManageAccount, accountManager, }: ProfileMenuWithAccountsProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { UseProfileDataOptions } from './types';
|
|
1
|
+
import type { UseProfileDataOptions, UseAccountManagerReturn } from './types';
|
|
2
2
|
interface ProfileMenuConnectedProps extends UseProfileDataOptions {
|
|
3
3
|
apiUrl: string;
|
|
4
4
|
onSignOut?: () => void;
|
|
5
5
|
onManageAccount?: () => void;
|
|
6
|
+
accountManager?: UseAccountManagerReturn;
|
|
6
7
|
}
|
|
7
|
-
export declare function ProfileMenuConnected({ apiUrl, onSignOut, onManageAccount, ...options }: ProfileMenuConnectedProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export declare function ProfileMenuConnected({ apiUrl, onSignOut, onManageAccount, accountManager, ...options }: ProfileMenuConnectedProps): import("react/jsx-runtime").JSX.Element;
|
|
8
9
|
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AuthProviderBaseProps } from 'react-oidc-context';
|
|
2
|
+
import { UserManager, type UserManagerSettings } from 'oidc-client-ts';
|
|
3
|
+
import type { KeycloakConfig } from './types';
|
|
4
|
+
interface SpryAuthContextValue {
|
|
5
|
+
keycloakConfig: KeycloakConfig;
|
|
6
|
+
userManager: UserManager;
|
|
7
|
+
syncBridgeUrl?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SpryAuthProviderProps extends AuthProviderBaseProps {
|
|
10
|
+
config: UserManagerSettings;
|
|
11
|
+
syncBridgeUrl?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function SpryAuthProvider({ children, config, syncBridgeUrl, onSigninCallback, matchSignoutCallback, onSignoutCallback, onRemoveUser, skipSigninCallback, }: SpryAuthProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export declare function useSpryAuthContext(): SpryAuthContextValue;
|
|
15
|
+
export {};
|
package/dist/TopBar.d.ts
CHANGED
|
@@ -6,4 +6,5 @@ import type { TopBarProps } from './types';
|
|
|
6
6
|
export declare function TopBar({ apiUrl, profileApiUrl, onSignOut, getAuthToken, profileHeaders, showAppsDropdown, showProfileMenu, appsRefetchInterval, // 5 minutes
|
|
7
7
|
appsCacheTime, // 5 minutes
|
|
8
8
|
profileRefetchInterval, // 5 minutes
|
|
9
|
-
profileCacheTime,
|
|
9
|
+
profileCacheTime, // 5 minutes
|
|
10
|
+
accountManager, }: TopBarProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account storage utilities for multi-account management
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ SECURITY WARNING: Uses localStorage for cross-app/cross-tab persistence.
|
|
5
|
+
*
|
|
6
|
+
* Tokens are stored in localStorage which persists across browser sessions and tabs.
|
|
7
|
+
* This enables multi-account switching across 20+ apps in the Spry ecosystem.
|
|
8
|
+
*
|
|
9
|
+
* XSS RISK: localStorage is vulnerable to XSS attacks. Consuming applications MUST:
|
|
10
|
+
* - Implement strict Content Security Policy (CSP) headers
|
|
11
|
+
* - Avoid eval(), inline scripts, and dangerous DOM manipulation
|
|
12
|
+
* - Sanitize all user inputs
|
|
13
|
+
* - Use trusted dependencies only
|
|
14
|
+
* - Regularly audit for XSS vulnerabilities
|
|
15
|
+
*
|
|
16
|
+
* See INTEGRATION.md for complete security recommendations.
|
|
17
|
+
*/
|
|
18
|
+
import type { AccountsState, StoredAccount, OIDCUser, StoredAccountMetadata } from './types';
|
|
19
|
+
/**
|
|
20
|
+
* Retrieve accounts state from localStorage
|
|
21
|
+
*/
|
|
22
|
+
export declare function getAccountsState(): AccountsState;
|
|
23
|
+
/**
|
|
24
|
+
* Retrieve all stored accounts from localStorage
|
|
25
|
+
*/
|
|
26
|
+
export declare function getAccounts(): StoredAccount[];
|
|
27
|
+
/**
|
|
28
|
+
* Persist accounts state to localStorage
|
|
29
|
+
*/
|
|
30
|
+
export declare function saveAccountsState(state: AccountsState): void;
|
|
31
|
+
/**
|
|
32
|
+
* Persist accounts to localStorage
|
|
33
|
+
*/
|
|
34
|
+
export declare function saveAccounts(accounts: StoredAccount[], activeAccountId?: string | null): void;
|
|
35
|
+
/**
|
|
36
|
+
* Add a new account to storage
|
|
37
|
+
* Returns the generated account ID
|
|
38
|
+
*/
|
|
39
|
+
export declare function addAccount(user: OIDCUser): string;
|
|
40
|
+
/**
|
|
41
|
+
* Remove an account from storage
|
|
42
|
+
*/
|
|
43
|
+
export declare function removeAccount(accountId: string): void;
|
|
44
|
+
/**
|
|
45
|
+
* Set an account as the active account
|
|
46
|
+
*/
|
|
47
|
+
export declare function setActiveAccount(accountId: string | null): void;
|
|
48
|
+
/**
|
|
49
|
+
* Get the currently active account
|
|
50
|
+
*/
|
|
51
|
+
export declare function getActiveAccount(): StoredAccount | null;
|
|
52
|
+
/**
|
|
53
|
+
* Get the active account ID
|
|
54
|
+
*/
|
|
55
|
+
export declare function getActiveAccountId(): string | null;
|
|
56
|
+
/**
|
|
57
|
+
* Update tokens for a specific account
|
|
58
|
+
*/
|
|
59
|
+
export declare function updateAccountTokens(accountId: string, user: OIDCUser): void;
|
|
60
|
+
/**
|
|
61
|
+
* Mark an account as stale (refresh token expired)
|
|
62
|
+
*/
|
|
63
|
+
export declare function markAccountStale(accountId: string): void;
|
|
64
|
+
/**
|
|
65
|
+
* Clear ALL stored accounts (used on logout or manual reset)
|
|
66
|
+
*/
|
|
67
|
+
export declare function clearAllAccounts(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Get account metadata without the user object
|
|
70
|
+
* Useful for debugging or display
|
|
71
|
+
*/
|
|
72
|
+
export declare function getAccountMetadata(accountId: string): StoredAccountMetadata | null;
|
|
73
|
+
/**
|
|
74
|
+
* Set flag indicating add account flow is in progress
|
|
75
|
+
*/
|
|
76
|
+
export declare function setAddAccountInProgress(inProgress: boolean): void;
|
|
77
|
+
/**
|
|
78
|
+
* Check if add account flow is in progress
|
|
79
|
+
*/
|
|
80
|
+
export declare function isAddAccountInProgress(): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Save return URL for redirect flow
|
|
83
|
+
*/
|
|
84
|
+
export declare function setReturnUrl(url: string): void;
|
|
85
|
+
/**
|
|
86
|
+
* Get return URL from redirect flow
|
|
87
|
+
*/
|
|
88
|
+
export declare function getReturnUrl(): string | null;
|
|
89
|
+
/**
|
|
90
|
+
* Clear return URL
|
|
91
|
+
*/
|
|
92
|
+
export declare function clearReturnUrl(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Save current user temporarily before redirect
|
|
95
|
+
*/
|
|
96
|
+
export declare function saveTempCurrentUser(user: OIDCUser): void;
|
|
97
|
+
/**
|
|
98
|
+
* Get temporarily saved current user
|
|
99
|
+
*/
|
|
100
|
+
export declare function getTempCurrentUser(): OIDCUser | null;
|
|
101
|
+
/**
|
|
102
|
+
* Clear temporarily saved current user
|
|
103
|
+
*/
|
|
104
|
+
export declare function clearTempCurrentUser(): void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage keys for multi-account management
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ SECURITY WARNING: Using localStorage for cross-app/cross-tab persistence.
|
|
5
|
+
* Tokens are stored in localStorage which is vulnerable to XSS attacks.
|
|
6
|
+
* Consuming applications MUST implement Content Security Policy (CSP) headers
|
|
7
|
+
* and other XSS mitigations. See INTEGRATION.md for security recommendations.
|
|
8
|
+
*/
|
|
9
|
+
export declare const STORAGE_KEYS: {
|
|
10
|
+
readonly ACCOUNTS: "spry_accounts";
|
|
11
|
+
readonly BRIDGE_ACCOUNTS: "spry_accounts_shared_state";
|
|
12
|
+
readonly ADD_ACCOUNT_IN_PROGRESS: "spry_add_account_in_progress";
|
|
13
|
+
readonly RETURN_URL: "spry_return_url";
|
|
14
|
+
readonly TEMP_CURRENT_USER: "spry_temp_current_user";
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Token validation and refresh configuration
|
|
18
|
+
*/
|
|
19
|
+
export declare const TOKEN_VALIDATION: {
|
|
20
|
+
readonly ACCESS_TOKEN_BUFFER_SECONDS: 60;
|
|
21
|
+
readonly REFRESH_TOKEN_BUFFER_SECONDS: 300;
|
|
22
|
+
readonly STALE_CHECK_INTERVAL_MS: 1000;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Error messages
|
|
26
|
+
*/
|
|
27
|
+
export declare const ERROR_MESSAGES: {
|
|
28
|
+
readonly INVALID_TOKEN: "Invalid or malformed token";
|
|
29
|
+
readonly TOKEN_DECODE_FAILED: "Failed to decode token";
|
|
30
|
+
readonly ACCOUNT_NOT_FOUND: "Account not found";
|
|
31
|
+
readonly SIGNIN_REDIRECT_FAILED: "Failed to initiate sign-in redirect";
|
|
32
|
+
readonly LOGOUT_FAILED: "Failed to logout from Keycloak, but account will be removed locally.";
|
|
33
|
+
readonly TOKEN_REFRESH_FAILED: "Failed to refresh token for account";
|
|
34
|
+
readonly SSO_CLEAR_FAILED: "Failed to clear SSO session. Please sign out first if prompted by Keycloak.";
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Warning/Info messages
|
|
38
|
+
*/
|
|
39
|
+
export declare const INFO_MESSAGES: {
|
|
40
|
+
readonly LOCALSTORAGE_XSS_WARNING: "⚠️ Tokens stored in localStorage are vulnerable to XSS. Ensure CSP headers are configured.";
|
|
41
|
+
readonly ACCOUNT_MARKED_STALE: "Account refresh token expired. Click to re-authenticate.";
|
|
42
|
+
readonly NO_ACCOUNTS: "No additional accounts stored. Your primary auth remains active.";
|
|
43
|
+
readonly REDIRECT_IN_PROGRESS: "Redirecting to Keycloak for authentication...";
|
|
44
|
+
};
|