@enterprisestandard/react 0.0.5-beta.20260115.2 → 0.0.5-beta.20260115.4
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/index.d.ts +2573 -41
- package/dist/index.js +3732 -144
- package/dist/index.js.map +1 -0
- package/package.json +3 -1
- package/dist/group-store.d.ts +0 -164
- package/dist/group-store.d.ts.map +0 -1
- package/dist/group-store.js +0 -127
- package/dist/iam.d.ts +0 -206
- package/dist/iam.d.ts.map +0 -1
- package/dist/iam.js +0 -680
- package/dist/index.d.ts.map +0 -1
- package/dist/session-store.d.ts +0 -179
- package/dist/session-store.d.ts.map +0 -1
- package/dist/session-store.js +0 -105
- package/dist/sso-server.d.ts +0 -13
- package/dist/sso-server.d.ts.map +0 -1
- package/dist/sso-server.js +0 -46
- package/dist/sso.d.ts +0 -104
- package/dist/sso.d.ts.map +0 -1
- package/dist/sso.js +0 -820
- package/dist/tenant-server.d.ts +0 -8
- package/dist/tenant-server.d.ts.map +0 -1
- package/dist/tenant-server.js +0 -6
- package/dist/tenant.d.ts +0 -280
- package/dist/tenant.d.ts.map +0 -1
- package/dist/tenant.js +0 -324
- package/dist/types/base-user.d.ts +0 -27
- package/dist/types/base-user.d.ts.map +0 -1
- package/dist/types/base-user.js +0 -1
- package/dist/types/enterprise-user.d.ts +0 -158
- package/dist/types/enterprise-user.d.ts.map +0 -1
- package/dist/types/enterprise-user.js +0 -1
- package/dist/types/oidc-schema.d.ts +0 -86
- package/dist/types/oidc-schema.d.ts.map +0 -1
- package/dist/types/oidc-schema.js +0 -328
- package/dist/types/scim-schema.d.ts +0 -419
- package/dist/types/scim-schema.d.ts.map +0 -1
- package/dist/types/scim-schema.js +0 -519
- package/dist/types/standard-schema.d.ts +0 -56
- package/dist/types/standard-schema.d.ts.map +0 -1
- package/dist/types/standard-schema.js +0 -1
- package/dist/types/user.d.ts +0 -41
- package/dist/types/user.d.ts.map +0 -1
- package/dist/types/user.js +0 -1
- package/dist/types/workload-schema.d.ts +0 -106
- package/dist/types/workload-schema.d.ts.map +0 -1
- package/dist/types/workload-schema.js +0 -208
- package/dist/ui/sign-in-loading.d.ts +0 -5
- package/dist/ui/sign-in-loading.d.ts.map +0 -1
- package/dist/ui/sign-in-loading.js +0 -8
- package/dist/ui/signed-in.d.ts +0 -3
- package/dist/ui/signed-in.d.ts.map +0 -1
- package/dist/ui/signed-in.js +0 -8
- package/dist/ui/signed-out.d.ts +0 -3
- package/dist/ui/signed-out.d.ts.map +0 -1
- package/dist/ui/signed-out.js +0 -8
- package/dist/ui/sso-provider.d.ts +0 -35
- package/dist/ui/sso-provider.d.ts.map +0 -1
- package/dist/ui/sso-provider.js +0 -275
- package/dist/user-store.d.ts +0 -161
- package/dist/user-store.d.ts.map +0 -1
- package/dist/user-store.js +0 -114
- package/dist/utils.d.ts +0 -9
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -23
- package/dist/vault.d.ts +0 -18
- package/dist/vault.d.ts.map +0 -1
- package/dist/vault.js +0 -22
- package/dist/workload-server.d.ts +0 -127
- package/dist/workload-server.d.ts.map +0 -1
- package/dist/workload-server.js +0 -167
- package/dist/workload-token-store.d.ts +0 -187
- package/dist/workload-token-store.d.ts.map +0 -1
- package/dist/workload-token-store.js +0 -95
- package/dist/workload.d.ts +0 -227
- package/dist/workload.d.ts.map +0 -1
- package/dist/workload.js +0 -691
package/dist/ui/sso-provider.js
DELETED
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
|
3
|
-
const CTX = createContext(undefined);
|
|
4
|
-
const generateStorageKey = (tenantId) => {
|
|
5
|
-
return `es-sso-user-${tenantId
|
|
6
|
-
.replace(/[^a-zA-Z0-9]/g, '-')
|
|
7
|
-
.replace(/-+/g, '-')
|
|
8
|
-
.replace(/^-|-$/g, '')}`;
|
|
9
|
-
};
|
|
10
|
-
export function SSOProvider({ tenantId, storage = 'memory', storageKey, userUrl, tokenUrl, refreshUrl, disableListener = false, children, }) {
|
|
11
|
-
const [user, setUserState] = useState(null);
|
|
12
|
-
const [isLoading, setIsLoading] = useState(!!userUrl);
|
|
13
|
-
const actualStorageKey = storageKey || (tenantId ? generateStorageKey(tenantId) : 'es-sso-user');
|
|
14
|
-
const isValidUser = useCallback((user) => {
|
|
15
|
-
if (!user || !tenantId)
|
|
16
|
-
return true;
|
|
17
|
-
return user.sso?.tenant?.id === tenantId;
|
|
18
|
-
}, [tenantId]);
|
|
19
|
-
const loadUserFromStorage = useCallback(() => {
|
|
20
|
-
if (storage === 'memory' || typeof window === 'undefined')
|
|
21
|
-
return null;
|
|
22
|
-
try {
|
|
23
|
-
const storageObject = storage === 'local' ? localStorage : sessionStorage;
|
|
24
|
-
const userData = storageObject.getItem(actualStorageKey);
|
|
25
|
-
if (!userData)
|
|
26
|
-
return null;
|
|
27
|
-
const parsedUser = JSON.parse(userData);
|
|
28
|
-
if (parsedUser.sso?.expires) {
|
|
29
|
-
parsedUser.sso.expires = new Date(parsedUser.sso.expires);
|
|
30
|
-
}
|
|
31
|
-
return isValidUser(parsedUser) ? parsedUser : null;
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
console.error('Error loading user from storage:', error);
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
}, [storage, actualStorageKey, isValidUser]);
|
|
38
|
-
const saveUserToStorage = useCallback((user) => {
|
|
39
|
-
if (storage === 'memory' || typeof window === 'undefined')
|
|
40
|
-
return;
|
|
41
|
-
try {
|
|
42
|
-
const storageObject = storage === 'local' ? localStorage : sessionStorage;
|
|
43
|
-
if (user === null) {
|
|
44
|
-
storageObject.removeItem(actualStorageKey);
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
storageObject.setItem(actualStorageKey, JSON.stringify(user));
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
catch (error) {
|
|
51
|
-
console.error('Error saving user to storage:', error);
|
|
52
|
-
}
|
|
53
|
-
}, [storage, actualStorageKey]);
|
|
54
|
-
const setUser = useCallback((newUser) => {
|
|
55
|
-
if (newUser && !isValidUser(newUser))
|
|
56
|
-
return;
|
|
57
|
-
setUserState(newUser);
|
|
58
|
-
saveUserToStorage(newUser);
|
|
59
|
-
}, [isValidUser, saveUserToStorage]);
|
|
60
|
-
const fetchUserFromUrl = useCallback(async () => {
|
|
61
|
-
if (!userUrl)
|
|
62
|
-
return;
|
|
63
|
-
setIsLoading(true);
|
|
64
|
-
try {
|
|
65
|
-
const response = await fetch(userUrl);
|
|
66
|
-
if (response.status === 401) {
|
|
67
|
-
setUserState(null);
|
|
68
|
-
saveUserToStorage(null);
|
|
69
|
-
setIsLoading(false);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (!response.ok) {
|
|
73
|
-
throw new Error(`Failed to fetch user: ${response.status} ${response.statusText}`);
|
|
74
|
-
}
|
|
75
|
-
const userData = (await response.json());
|
|
76
|
-
if (userData.sso?.expires && typeof userData.sso.expires === 'string') {
|
|
77
|
-
userData.sso.expires = new Date(userData.sso.expires);
|
|
78
|
-
}
|
|
79
|
-
if (isValidUser(userData)) {
|
|
80
|
-
setUserState(userData);
|
|
81
|
-
saveUserToStorage(userData);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
console.error('Error fetching user from URL:', error);
|
|
86
|
-
}
|
|
87
|
-
finally {
|
|
88
|
-
setIsLoading(false);
|
|
89
|
-
}
|
|
90
|
-
}, [userUrl, isValidUser, saveUserToStorage]);
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
const storedUser = loadUserFromStorage();
|
|
93
|
-
if (storedUser) {
|
|
94
|
-
setUserState(storedUser);
|
|
95
|
-
}
|
|
96
|
-
if (userUrl) {
|
|
97
|
-
fetchUserFromUrl();
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
setIsLoading(false);
|
|
101
|
-
}
|
|
102
|
-
}, [loadUserFromStorage, userUrl, fetchUserFromUrl]);
|
|
103
|
-
useEffect(() => {
|
|
104
|
-
if (disableListener || storage === 'memory')
|
|
105
|
-
return;
|
|
106
|
-
const handleStorageChange = (event) => {
|
|
107
|
-
if (event.key !== actualStorageKey)
|
|
108
|
-
return;
|
|
109
|
-
if (event.newValue === null) {
|
|
110
|
-
setUserState(null);
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
try {
|
|
114
|
-
const parsedUser = JSON.parse(event.newValue);
|
|
115
|
-
if (parsedUser.sso?.expires) {
|
|
116
|
-
parsedUser.sso.expires = new Date(parsedUser.sso.expires);
|
|
117
|
-
}
|
|
118
|
-
if (isValidUser(parsedUser)) {
|
|
119
|
-
setUserState(parsedUser);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
catch (error) {
|
|
123
|
-
console.error('Error parsing user from storage event:', error);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
const handleLogout = () => {
|
|
128
|
-
setUserState(null);
|
|
129
|
-
};
|
|
130
|
-
window.addEventListener('storage', handleStorageChange);
|
|
131
|
-
window.addEventListener('es-sso-logout', handleLogout);
|
|
132
|
-
return () => {
|
|
133
|
-
window.removeEventListener('storage', handleStorageChange);
|
|
134
|
-
window.removeEventListener('es-sso-logout', handleLogout);
|
|
135
|
-
};
|
|
136
|
-
}, [disableListener, storage, actualStorageKey, isValidUser]);
|
|
137
|
-
const contextValue = {
|
|
138
|
-
user,
|
|
139
|
-
setUser,
|
|
140
|
-
isLoading,
|
|
141
|
-
tokenUrl,
|
|
142
|
-
refreshUrl,
|
|
143
|
-
};
|
|
144
|
-
return _jsx(CTX.Provider, { value: contextValue, children: children });
|
|
145
|
-
}
|
|
146
|
-
export function useUser() {
|
|
147
|
-
const context = useContext(CTX);
|
|
148
|
-
if (context === undefined) {
|
|
149
|
-
throw new Error('useUser must be used within a SSOProvider');
|
|
150
|
-
}
|
|
151
|
-
return context;
|
|
152
|
-
}
|
|
153
|
-
export function useToken() {
|
|
154
|
-
const context = useContext(CTX);
|
|
155
|
-
if (context === undefined) {
|
|
156
|
-
throw new Error('useToken must be used within a SSOProvider');
|
|
157
|
-
}
|
|
158
|
-
const { tokenUrl, refreshUrl } = context;
|
|
159
|
-
if (!tokenUrl || !refreshUrl) {
|
|
160
|
-
throw new Error('useToken requires that a "tokenUrl" and "refreshUrl" be set in the SSOProvider');
|
|
161
|
-
}
|
|
162
|
-
const [token, setToken] = useState(null);
|
|
163
|
-
const [expires, setExpires] = useState(null);
|
|
164
|
-
const [isLoading, setIsLoading] = useState(!!tokenUrl);
|
|
165
|
-
const [error, setError] = useState(null);
|
|
166
|
-
const fetchJwt = useCallback(async (url) => {
|
|
167
|
-
setIsLoading(true);
|
|
168
|
-
setError(null);
|
|
169
|
-
try {
|
|
170
|
-
const response = await fetch(url);
|
|
171
|
-
if (response.status === 401) {
|
|
172
|
-
context.setUser(null);
|
|
173
|
-
setToken(null);
|
|
174
|
-
setExpires(null);
|
|
175
|
-
setIsLoading(false);
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
if (!response.ok) {
|
|
179
|
-
throw new Error(`Failed to fetch JWT: ${response.status} ${response.statusText}`);
|
|
180
|
-
}
|
|
181
|
-
const data = (await response.json());
|
|
182
|
-
setToken(data.token);
|
|
183
|
-
setExpires(new Date(data.expires));
|
|
184
|
-
}
|
|
185
|
-
catch (err) {
|
|
186
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
187
|
-
setError(error);
|
|
188
|
-
setToken(null);
|
|
189
|
-
setExpires(null);
|
|
190
|
-
console.error('Error fetching JWT:', error);
|
|
191
|
-
}
|
|
192
|
-
finally {
|
|
193
|
-
setIsLoading(false);
|
|
194
|
-
}
|
|
195
|
-
}, [context]);
|
|
196
|
-
const refresh = useCallback(async () => {
|
|
197
|
-
const url = refreshUrl || tokenUrl;
|
|
198
|
-
if (!url) {
|
|
199
|
-
console.warn('No tokenUrl or refreshUrl provided');
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
await fetchJwt(url);
|
|
203
|
-
}, [refreshUrl, tokenUrl, fetchJwt]);
|
|
204
|
-
useEffect(() => {
|
|
205
|
-
if (!tokenUrl) {
|
|
206
|
-
setIsLoading(false);
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
fetchJwt(tokenUrl);
|
|
210
|
-
}, [tokenUrl, fetchJwt]);
|
|
211
|
-
useEffect(() => {
|
|
212
|
-
if (!expires || !refreshUrl)
|
|
213
|
-
return;
|
|
214
|
-
const checkExpiration = () => {
|
|
215
|
-
const now = new Date();
|
|
216
|
-
const timeUntilExpiry = expires.getTime() - now.getTime();
|
|
217
|
-
// Refresh 1 minute before expiration
|
|
218
|
-
if (timeUntilExpiry <= 60000 && timeUntilExpiry > 0) {
|
|
219
|
-
refresh();
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
|
-
// Check immediately
|
|
223
|
-
checkExpiration();
|
|
224
|
-
// Check every 30 seconds
|
|
225
|
-
const interval = setInterval(checkExpiration, 30000);
|
|
226
|
-
return () => clearInterval(interval);
|
|
227
|
-
}, [expires, refreshUrl, refresh]);
|
|
228
|
-
return {
|
|
229
|
-
token,
|
|
230
|
-
isLoading,
|
|
231
|
-
error,
|
|
232
|
-
refresh,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
export async function logout(logoutUrl) {
|
|
236
|
-
try {
|
|
237
|
-
// Make AJAX logout call
|
|
238
|
-
const response = await fetch(logoutUrl, {
|
|
239
|
-
headers: { Accept: 'application/json' },
|
|
240
|
-
});
|
|
241
|
-
if (!response.ok) {
|
|
242
|
-
return { success: false, error: `HTTP ${response.status}` };
|
|
243
|
-
}
|
|
244
|
-
const data = await response.json();
|
|
245
|
-
if (!data.success) {
|
|
246
|
-
return { success: false, error: data.message || 'Logout failed' };
|
|
247
|
-
}
|
|
248
|
-
// Clear managed storage keys (all es-sso-user-* keys)
|
|
249
|
-
if (typeof window !== 'undefined') {
|
|
250
|
-
// Clear localStorage
|
|
251
|
-
for (let i = localStorage.length - 1; i >= 0; i--) {
|
|
252
|
-
const key = localStorage.key(i);
|
|
253
|
-
if (key?.startsWith('es-sso-user')) {
|
|
254
|
-
localStorage.removeItem(key);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
// Clear sessionStorage
|
|
258
|
-
for (let i = sessionStorage.length - 1; i >= 0; i--) {
|
|
259
|
-
const key = sessionStorage.key(i);
|
|
260
|
-
if (key?.startsWith('es-sso-user')) {
|
|
261
|
-
sessionStorage.removeItem(key);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
// Dispatch custom event for same-tab state updates
|
|
265
|
-
window.dispatchEvent(new CustomEvent('es-sso-logout'));
|
|
266
|
-
}
|
|
267
|
-
return { success: true };
|
|
268
|
-
}
|
|
269
|
-
catch (error) {
|
|
270
|
-
return {
|
|
271
|
-
success: false,
|
|
272
|
-
error: error instanceof Error ? error.message : 'Network error',
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
}
|
package/dist/user-store.d.ts
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User storage for persisting user profiles from SSO authentication.
|
|
3
|
-
*
|
|
4
|
-
* User stores are optional - the package works with JWT cookies alone.
|
|
5
|
-
* User stores are useful when you want to:
|
|
6
|
-
* - Cache user profiles for fast lookup
|
|
7
|
-
* - Store users close to your application (in-memory, Redis, etc.)
|
|
8
|
-
* - Avoid custom IAM/SCIM integration for simple use cases
|
|
9
|
-
*
|
|
10
|
-
* ## When to Use UserStore vs IAM
|
|
11
|
-
*
|
|
12
|
-
* **Use UserStore when:**
|
|
13
|
-
* - You just need fast user lookups without external systems
|
|
14
|
-
* - Users are managed by an external IdP and you just cache them locally
|
|
15
|
-
* - You want simple in-memory or Redis storage
|
|
16
|
-
*
|
|
17
|
-
* **Use IAM (SCIM) when:**
|
|
18
|
-
* - You need to provision users to an external identity provider
|
|
19
|
-
* - You need custom user attributes beyond what SSO provides
|
|
20
|
-
* - You need to sync users with enterprise directories
|
|
21
|
-
*
|
|
22
|
-
* ## Example Usage
|
|
23
|
-
*
|
|
24
|
-
* ```typescript
|
|
25
|
-
* import { sso, InMemoryUserStore } from '@enterprisestandard/react/server';
|
|
26
|
-
*
|
|
27
|
-
* const userStore = new InMemoryUserStore();
|
|
28
|
-
*
|
|
29
|
-
* const auth = sso({
|
|
30
|
-
* // ... other config
|
|
31
|
-
* user_store: userStore,
|
|
32
|
-
* });
|
|
33
|
-
*
|
|
34
|
-
* // Later, look up users
|
|
35
|
-
* const user = await userStore.get('user-sub-id');
|
|
36
|
-
* const userByEmail = await userStore.getByEmail('user@example.com');
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
import type { User } from './types/user';
|
|
40
|
-
/**
|
|
41
|
-
* Stored user data with required id and tracking metadata.
|
|
42
|
-
*
|
|
43
|
-
* Extends the SSO User type with:
|
|
44
|
-
* - Required `id` (the `sub` claim from the IdP)
|
|
45
|
-
* - Timestamps for tracking when users were first seen and last updated
|
|
46
|
-
* - Optional custom extended data
|
|
47
|
-
*
|
|
48
|
-
* @template TExtended - Type-safe custom data that consumers can add to users
|
|
49
|
-
*/
|
|
50
|
-
export type StoredUser<TExtended = {}> = User & {
|
|
51
|
-
/**
|
|
52
|
-
* Required unique identifier (the `sub` claim from the IdP).
|
|
53
|
-
* This is the primary key for user storage.
|
|
54
|
-
*/
|
|
55
|
-
id: string;
|
|
56
|
-
/**
|
|
57
|
-
* Timestamp when the user was first stored.
|
|
58
|
-
*/
|
|
59
|
-
createdAt: Date;
|
|
60
|
-
/**
|
|
61
|
-
* Timestamp when the user was last updated (e.g., on re-login).
|
|
62
|
-
*/
|
|
63
|
-
updatedAt: Date;
|
|
64
|
-
} & TExtended;
|
|
65
|
-
/**
|
|
66
|
-
* Abstract interface for user storage backends.
|
|
67
|
-
*
|
|
68
|
-
* Consumers can implement this interface to use different storage backends:
|
|
69
|
-
* - In-memory (for development/testing)
|
|
70
|
-
* - Redis (for production with fast lookups)
|
|
71
|
-
* - Database (PostgreSQL, MySQL, etc.)
|
|
72
|
-
*
|
|
73
|
-
* @template TExtended - Type-safe custom data that consumers can add to users
|
|
74
|
-
*
|
|
75
|
-
* @example
|
|
76
|
-
* ```typescript
|
|
77
|
-
* // Custom user data
|
|
78
|
-
* type MyUserData = {
|
|
79
|
-
* department: string;
|
|
80
|
-
* roles: string[];
|
|
81
|
-
* };
|
|
82
|
-
*
|
|
83
|
-
* // Implement custom store
|
|
84
|
-
* class RedisUserStore implements UserStore<MyUserData> {
|
|
85
|
-
* async get(sub: string): Promise<StoredUser<MyUserData> | null> {
|
|
86
|
-
* const data = await redis.get(`user:${sub}`);
|
|
87
|
-
* return data ? JSON.parse(data) : null;
|
|
88
|
-
* }
|
|
89
|
-
* // ... other methods
|
|
90
|
-
* }
|
|
91
|
-
* ```
|
|
92
|
-
*/
|
|
93
|
-
export interface UserStore<TExtended = {}> {
|
|
94
|
-
/**
|
|
95
|
-
* Retrieve a user by their subject identifier (sub).
|
|
96
|
-
*
|
|
97
|
-
* @param sub - The user's unique identifier from the IdP
|
|
98
|
-
* @returns The user if found, null otherwise
|
|
99
|
-
*/
|
|
100
|
-
get(sub: string): Promise<StoredUser<TExtended> | null>;
|
|
101
|
-
/**
|
|
102
|
-
* Retrieve a user by their email address.
|
|
103
|
-
*
|
|
104
|
-
* @param email - The user's email address
|
|
105
|
-
* @returns The user if found, null otherwise
|
|
106
|
-
*/
|
|
107
|
-
getByEmail(email: string): Promise<StoredUser<TExtended> | null>;
|
|
108
|
-
/**
|
|
109
|
-
* Retrieve a user by their username.
|
|
110
|
-
*
|
|
111
|
-
* @param userName - The user's username
|
|
112
|
-
* @returns The user if found, null otherwise
|
|
113
|
-
*/
|
|
114
|
-
getByUserName(userName: string): Promise<StoredUser<TExtended> | null>;
|
|
115
|
-
/**
|
|
116
|
-
* Create or update a user in the store.
|
|
117
|
-
*
|
|
118
|
-
* If a user with the same `id` (sub) exists, it will be updated.
|
|
119
|
-
* Otherwise, a new user will be created.
|
|
120
|
-
*
|
|
121
|
-
* @param user - The user data to store
|
|
122
|
-
*/
|
|
123
|
-
upsert(user: StoredUser<TExtended>): Promise<void>;
|
|
124
|
-
/**
|
|
125
|
-
* Delete a user by their subject identifier (sub).
|
|
126
|
-
*
|
|
127
|
-
* @param sub - The user's unique identifier to delete
|
|
128
|
-
*/
|
|
129
|
-
delete(sub: string): Promise<void>;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* In-memory user store implementation using Maps.
|
|
133
|
-
*
|
|
134
|
-
* Suitable for:
|
|
135
|
-
* - Development and testing
|
|
136
|
-
* - Single-server deployments
|
|
137
|
-
* - Applications without high availability requirements
|
|
138
|
-
*
|
|
139
|
-
* NOT suitable for:
|
|
140
|
-
* - Multi-server deployments (users not shared)
|
|
141
|
-
* - High availability scenarios (users lost on restart)
|
|
142
|
-
* - Production applications with distributed architecture
|
|
143
|
-
*
|
|
144
|
-
* For production, implement UserStore with Redis or a database.
|
|
145
|
-
*
|
|
146
|
-
* @template TExtended - Type-safe custom data that consumers can add to users
|
|
147
|
-
*/
|
|
148
|
-
export declare class InMemoryUserStore<TExtended = {}> implements UserStore<TExtended> {
|
|
149
|
-
/** Primary storage: sub -> user */
|
|
150
|
-
private users;
|
|
151
|
-
/** Secondary index: email -> sub */
|
|
152
|
-
private emailIndex;
|
|
153
|
-
/** Secondary index: userName -> sub */
|
|
154
|
-
private userNameIndex;
|
|
155
|
-
get(sub: string): Promise<StoredUser<TExtended> | null>;
|
|
156
|
-
getByEmail(email: string): Promise<StoredUser<TExtended> | null>;
|
|
157
|
-
getByUserName(userName: string): Promise<StoredUser<TExtended> | null>;
|
|
158
|
-
upsert(user: StoredUser<TExtended>): Promise<void>;
|
|
159
|
-
delete(sub: string): Promise<void>;
|
|
160
|
-
}
|
|
161
|
-
//# sourceMappingURL=user-store.d.ts.map
|
package/dist/user-store.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"user-store.d.ts","sourceRoot":"","sources":["../src/user-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,CAAC,SAAS,GAAG,EAAE,IAAI,IAAI,GAAG;IAC9C;;;OAGG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,SAAS,EAAE,IAAI,CAAC;IAEhB;;OAEG;IACH,SAAS,EAAE,IAAI,CAAC;CACjB,GAAG,SAAS,CAAC;AAEd;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,SAAS,CAAC,SAAS,GAAG,EAAE;IACvC;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC;IAExD;;;;;OAKG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC;IAEjE;;;;;OAKG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC;IAEvE;;;;;;;OAOG;IACH,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnD;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,iBAAiB,CAAC,SAAS,GAAG,EAAE,CAAE,YAAW,SAAS,CAAC,SAAS,CAAC;IAC5E,mCAAmC;IACnC,OAAO,CAAC,KAAK,CAA4C;IAEzD,oCAAoC;IACpC,OAAO,CAAC,UAAU,CAA6B;IAE/C,uCAAuC;IACvC,OAAO,CAAC,aAAa,CAA6B;IAE5C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;IAIvD,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;IAMhE,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;IAMtE,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBlD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAazC"}
|
package/dist/user-store.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User storage for persisting user profiles from SSO authentication.
|
|
3
|
-
*
|
|
4
|
-
* User stores are optional - the package works with JWT cookies alone.
|
|
5
|
-
* User stores are useful when you want to:
|
|
6
|
-
* - Cache user profiles for fast lookup
|
|
7
|
-
* - Store users close to your application (in-memory, Redis, etc.)
|
|
8
|
-
* - Avoid custom IAM/SCIM integration for simple use cases
|
|
9
|
-
*
|
|
10
|
-
* ## When to Use UserStore vs IAM
|
|
11
|
-
*
|
|
12
|
-
* **Use UserStore when:**
|
|
13
|
-
* - You just need fast user lookups without external systems
|
|
14
|
-
* - Users are managed by an external IdP and you just cache them locally
|
|
15
|
-
* - You want simple in-memory or Redis storage
|
|
16
|
-
*
|
|
17
|
-
* **Use IAM (SCIM) when:**
|
|
18
|
-
* - You need to provision users to an external identity provider
|
|
19
|
-
* - You need custom user attributes beyond what SSO provides
|
|
20
|
-
* - You need to sync users with enterprise directories
|
|
21
|
-
*
|
|
22
|
-
* ## Example Usage
|
|
23
|
-
*
|
|
24
|
-
* ```typescript
|
|
25
|
-
* import { sso, InMemoryUserStore } from '@enterprisestandard/react/server';
|
|
26
|
-
*
|
|
27
|
-
* const userStore = new InMemoryUserStore();
|
|
28
|
-
*
|
|
29
|
-
* const auth = sso({
|
|
30
|
-
* // ... other config
|
|
31
|
-
* user_store: userStore,
|
|
32
|
-
* });
|
|
33
|
-
*
|
|
34
|
-
* // Later, look up users
|
|
35
|
-
* const user = await userStore.get('user-sub-id');
|
|
36
|
-
* const userByEmail = await userStore.getByEmail('user@example.com');
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
/**
|
|
40
|
-
* In-memory user store implementation using Maps.
|
|
41
|
-
*
|
|
42
|
-
* Suitable for:
|
|
43
|
-
* - Development and testing
|
|
44
|
-
* - Single-server deployments
|
|
45
|
-
* - Applications without high availability requirements
|
|
46
|
-
*
|
|
47
|
-
* NOT suitable for:
|
|
48
|
-
* - Multi-server deployments (users not shared)
|
|
49
|
-
* - High availability scenarios (users lost on restart)
|
|
50
|
-
* - Production applications with distributed architecture
|
|
51
|
-
*
|
|
52
|
-
* For production, implement UserStore with Redis or a database.
|
|
53
|
-
*
|
|
54
|
-
* @template TExtended - Type-safe custom data that consumers can add to users
|
|
55
|
-
*/
|
|
56
|
-
export class InMemoryUserStore {
|
|
57
|
-
constructor() {
|
|
58
|
-
/** Primary storage: sub -> user */
|
|
59
|
-
this.users = new Map();
|
|
60
|
-
/** Secondary index: email -> sub */
|
|
61
|
-
this.emailIndex = new Map();
|
|
62
|
-
/** Secondary index: userName -> sub */
|
|
63
|
-
this.userNameIndex = new Map();
|
|
64
|
-
}
|
|
65
|
-
async get(sub) {
|
|
66
|
-
return this.users.get(sub) ?? null;
|
|
67
|
-
}
|
|
68
|
-
async getByEmail(email) {
|
|
69
|
-
const sub = this.emailIndex.get(email.toLowerCase());
|
|
70
|
-
if (!sub)
|
|
71
|
-
return null;
|
|
72
|
-
return this.users.get(sub) ?? null;
|
|
73
|
-
}
|
|
74
|
-
async getByUserName(userName) {
|
|
75
|
-
const sub = this.userNameIndex.get(userName.toLowerCase());
|
|
76
|
-
if (!sub)
|
|
77
|
-
return null;
|
|
78
|
-
return this.users.get(sub) ?? null;
|
|
79
|
-
}
|
|
80
|
-
async upsert(user) {
|
|
81
|
-
const existing = this.users.get(user.id);
|
|
82
|
-
// Clean up old indexes if email or userName changed
|
|
83
|
-
if (existing) {
|
|
84
|
-
if (existing.email && existing.email.toLowerCase() !== user.email?.toLowerCase()) {
|
|
85
|
-
this.emailIndex.delete(existing.email.toLowerCase());
|
|
86
|
-
}
|
|
87
|
-
if (existing.userName && existing.userName.toLowerCase() !== user.userName?.toLowerCase()) {
|
|
88
|
-
this.userNameIndex.delete(existing.userName.toLowerCase());
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
// Store the user
|
|
92
|
-
this.users.set(user.id, user);
|
|
93
|
-
// Update indexes
|
|
94
|
-
if (user.email) {
|
|
95
|
-
this.emailIndex.set(user.email.toLowerCase(), user.id);
|
|
96
|
-
}
|
|
97
|
-
if (user.userName) {
|
|
98
|
-
this.userNameIndex.set(user.userName.toLowerCase(), user.id);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
async delete(sub) {
|
|
102
|
-
const user = this.users.get(sub);
|
|
103
|
-
if (user) {
|
|
104
|
-
// Clean up indexes
|
|
105
|
-
if (user.email) {
|
|
106
|
-
this.emailIndex.delete(user.email.toLowerCase());
|
|
107
|
-
}
|
|
108
|
-
if (user.userName) {
|
|
109
|
-
this.userNameIndex.delete(user.userName.toLowerCase());
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
this.users.delete(sub);
|
|
113
|
-
}
|
|
114
|
-
}
|
package/dist/utils.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { EnterpriseStandard } from '.';
|
|
2
|
-
export declare function must<T>(value: T | undefined | null, message?: string): T;
|
|
3
|
-
export declare function setDefaultInstance(es: EnterpriseStandard): void;
|
|
4
|
-
export declare function getDefaultInstance(): EnterpriseStandard | undefined;
|
|
5
|
-
/**
|
|
6
|
-
* If an es is defined, then return it, otherwise return the defaultEnterpriseStandard
|
|
7
|
-
*/
|
|
8
|
-
export declare function getES(es?: EnterpriseStandard): EnterpriseStandard;
|
|
9
|
-
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,GAAG,CAAC;AAI5C,wBAAgB,IAAI,CAAC,CAAC,EACpB,KAAK,EAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAC3B,OAAO,SAA2D,GACjE,CAAC,CAKH;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,kBAAkB,QAExD;AAED,wBAAgB,kBAAkB,mCAEjC;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,EAAE,CAAC,EAAE,kBAAkB,sBAI5C"}
|
package/dist/utils.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
let defaultInstance;
|
|
2
|
-
export function must(value, message = 'Assertion failed. Required value is null or undefined.') {
|
|
3
|
-
if (value === undefined || value === null) {
|
|
4
|
-
throw new Error(message);
|
|
5
|
-
}
|
|
6
|
-
return value;
|
|
7
|
-
}
|
|
8
|
-
export function setDefaultInstance(es) {
|
|
9
|
-
defaultInstance = es;
|
|
10
|
-
}
|
|
11
|
-
export function getDefaultInstance() {
|
|
12
|
-
return defaultInstance;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* If an es is defined, then return it, otherwise return the defaultEnterpriseStandard
|
|
16
|
-
*/
|
|
17
|
-
export function getES(es) {
|
|
18
|
-
if (es)
|
|
19
|
-
return es;
|
|
20
|
-
if (defaultInstance)
|
|
21
|
-
return defaultInstance;
|
|
22
|
-
throw new Error(`TODO standardize the error message when there isn't a default EntepriseStandard`);
|
|
23
|
-
}
|
package/dist/vault.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
type Secret<T> = {
|
|
2
|
-
data: T;
|
|
3
|
-
metadata: MetaData;
|
|
4
|
-
};
|
|
5
|
-
type MetaData = {
|
|
6
|
-
created_time: string;
|
|
7
|
-
deletion_time: string;
|
|
8
|
-
destroyed: boolean;
|
|
9
|
-
version: number;
|
|
10
|
-
};
|
|
11
|
-
export type Vault = {
|
|
12
|
-
url: string;
|
|
13
|
-
getFullSecret: <T>(path: string, token: string) => Promise<Secret<T>>;
|
|
14
|
-
getSecret: <T>(path: string, token: string) => Promise<T>;
|
|
15
|
-
};
|
|
16
|
-
export declare function vault(url: string): Vault;
|
|
17
|
-
export {};
|
|
18
|
-
//# sourceMappingURL=vault.d.ts.map
|
package/dist/vault.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"vault.d.ts","sourceRoot":"","sources":["../src/vault.ts"],"names":[],"mappings":"AAAA,KAAK,MAAM,CAAC,CAAC,IAAI;IACf,IAAI,EAAE,CAAC,CAAC;IACR,QAAQ,EAAE,QAAQ,CAAC;CACpB,CAAC;AAEF,KAAK,QAAQ,GAAG;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACtE,SAAS,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CAC3D,CAAC;AAEF,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAqBxC"}
|
package/dist/vault.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export function vault(url) {
|
|
2
|
-
async function getFullSecret(path, token) {
|
|
3
|
-
const resp = await fetch(`${url}/${path}`, { headers: { 'X-Vault-Token': token } });
|
|
4
|
-
if (resp.status !== 200) {
|
|
5
|
-
throw new Error(`Vault returned invalid status, ${resp.status}: '${resp.statusText}' from URL: ${url}`);
|
|
6
|
-
}
|
|
7
|
-
try {
|
|
8
|
-
const secret = await resp.json();
|
|
9
|
-
return secret.data;
|
|
10
|
-
}
|
|
11
|
-
catch (cause) {
|
|
12
|
-
throw new Error('Error retrieving secret', { cause });
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return {
|
|
16
|
-
url,
|
|
17
|
-
getFullSecret,
|
|
18
|
-
getSecret: async (path, token) => {
|
|
19
|
-
return (await getFullSecret(path, token)).data;
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
}
|