@nimbleflux/fluxbase-sdk-react 2026.3.6-rc.1
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/.nvmrc +1 -0
- package/README-ADMIN.md +1076 -0
- package/README.md +195 -0
- package/examples/AdminDashboard.tsx +513 -0
- package/examples/README.md +163 -0
- package/package.json +66 -0
- package/src/context.test.tsx +147 -0
- package/src/context.tsx +33 -0
- package/src/index.test.ts +255 -0
- package/src/index.ts +175 -0
- package/src/test-setup.ts +22 -0
- package/src/test-utils.tsx +215 -0
- package/src/use-admin-auth.test.ts +175 -0
- package/src/use-admin-auth.ts +187 -0
- package/src/use-admin-hooks.test.ts +457 -0
- package/src/use-admin-hooks.ts +309 -0
- package/src/use-auth-config.test.ts +145 -0
- package/src/use-auth-config.ts +101 -0
- package/src/use-auth.test.ts +313 -0
- package/src/use-auth.ts +164 -0
- package/src/use-captcha.test.ts +273 -0
- package/src/use-captcha.ts +250 -0
- package/src/use-client-keys.test.ts +286 -0
- package/src/use-client-keys.ts +185 -0
- package/src/use-graphql.test.ts +424 -0
- package/src/use-graphql.ts +392 -0
- package/src/use-query.test.ts +348 -0
- package/src/use-query.ts +211 -0
- package/src/use-realtime.test.ts +359 -0
- package/src/use-realtime.ts +180 -0
- package/src/use-saml.test.ts +269 -0
- package/src/use-saml.ts +221 -0
- package/src/use-storage.test.ts +549 -0
- package/src/use-storage.ts +508 -0
- package/src/use-table-export.ts +481 -0
- package/src/use-users.test.ts +264 -0
- package/src/use-users.ts +198 -0
- package/tsconfig.json +28 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsup.config.ts +11 -0
- package/typedoc.json +33 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for SAML SSO hooks
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
6
|
+
import { renderHook, waitFor, act } from '@testing-library/react';
|
|
7
|
+
import {
|
|
8
|
+
useSAMLProviders,
|
|
9
|
+
useGetSAMLLoginUrl,
|
|
10
|
+
useSignInWithSAML,
|
|
11
|
+
useHandleSAMLCallback,
|
|
12
|
+
useSAMLMetadataUrl,
|
|
13
|
+
} from './use-saml';
|
|
14
|
+
import { createMockClient, createWrapper, createTestQueryClient } from './test-utils';
|
|
15
|
+
|
|
16
|
+
describe('useSAMLProviders', () => {
|
|
17
|
+
it('should fetch SAML providers', async () => {
|
|
18
|
+
const mockProviders = [
|
|
19
|
+
{ id: '1', name: 'okta', display_name: 'Okta' },
|
|
20
|
+
{ id: '2', name: 'azure', display_name: 'Azure AD' },
|
|
21
|
+
];
|
|
22
|
+
const getSAMLProvidersMock = vi.fn().mockResolvedValue({
|
|
23
|
+
data: { providers: mockProviders },
|
|
24
|
+
error: null,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const client = createMockClient({
|
|
28
|
+
auth: { getSAMLProviders: getSAMLProvidersMock },
|
|
29
|
+
} as any);
|
|
30
|
+
|
|
31
|
+
const { result } = renderHook(
|
|
32
|
+
() => useSAMLProviders(),
|
|
33
|
+
{ wrapper: createWrapper(client) }
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
37
|
+
expect(result.current.data).toEqual(mockProviders);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should throw error on fetch failure', async () => {
|
|
41
|
+
const error = new Error('Failed to fetch');
|
|
42
|
+
const getSAMLProvidersMock = vi.fn().mockResolvedValue({ data: null, error });
|
|
43
|
+
|
|
44
|
+
const client = createMockClient({
|
|
45
|
+
auth: { getSAMLProviders: getSAMLProvidersMock },
|
|
46
|
+
} as any);
|
|
47
|
+
|
|
48
|
+
const { result } = renderHook(
|
|
49
|
+
() => useSAMLProviders(),
|
|
50
|
+
{ wrapper: createWrapper(client) }
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
await waitFor(() => expect(result.current.isError).toBe(true));
|
|
54
|
+
expect(result.current.error).toBe(error);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('useGetSAMLLoginUrl', () => {
|
|
59
|
+
it('should get SAML login URL', async () => {
|
|
60
|
+
const mockUrl = 'https://idp.example.com/sso/saml?SAMLRequest=...';
|
|
61
|
+
const getSAMLLoginUrlMock = vi.fn().mockResolvedValue({
|
|
62
|
+
data: { url: mockUrl },
|
|
63
|
+
error: null,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const client = createMockClient({
|
|
67
|
+
auth: { getSAMLLoginUrl: getSAMLLoginUrlMock },
|
|
68
|
+
} as any);
|
|
69
|
+
|
|
70
|
+
const { result } = renderHook(
|
|
71
|
+
() => useGetSAMLLoginUrl(),
|
|
72
|
+
{ wrapper: createWrapper(client) }
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
await act(async () => {
|
|
76
|
+
await result.current.mutateAsync({
|
|
77
|
+
provider: 'okta',
|
|
78
|
+
options: { redirectUrl: 'https://app.example.com/callback' },
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(getSAMLLoginUrlMock).toHaveBeenCalledWith('okta', {
|
|
83
|
+
redirectUrl: 'https://app.example.com/callback',
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should get SAML login URL without options', async () => {
|
|
88
|
+
const getSAMLLoginUrlMock = vi.fn().mockResolvedValue({
|
|
89
|
+
data: { url: 'https://idp.example.com/sso' },
|
|
90
|
+
error: null,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const client = createMockClient({
|
|
94
|
+
auth: { getSAMLLoginUrl: getSAMLLoginUrlMock },
|
|
95
|
+
} as any);
|
|
96
|
+
|
|
97
|
+
const { result } = renderHook(
|
|
98
|
+
() => useGetSAMLLoginUrl(),
|
|
99
|
+
{ wrapper: createWrapper(client) }
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
await act(async () => {
|
|
103
|
+
await result.current.mutateAsync({ provider: 'okta' });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(getSAMLLoginUrlMock).toHaveBeenCalledWith('okta', undefined);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('useSignInWithSAML', () => {
|
|
111
|
+
it('should initiate SAML sign in', async () => {
|
|
112
|
+
const signInWithSAMLMock = vi.fn().mockResolvedValue({ data: null, error: null });
|
|
113
|
+
|
|
114
|
+
const client = createMockClient({
|
|
115
|
+
auth: { signInWithSAML: signInWithSAMLMock },
|
|
116
|
+
} as any);
|
|
117
|
+
|
|
118
|
+
const { result } = renderHook(
|
|
119
|
+
() => useSignInWithSAML(),
|
|
120
|
+
{ wrapper: createWrapper(client) }
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
await act(async () => {
|
|
124
|
+
await result.current.mutateAsync({ provider: 'okta' });
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(signInWithSAMLMock).toHaveBeenCalledWith('okta', undefined);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should pass options to sign in', async () => {
|
|
131
|
+
const signInWithSAMLMock = vi.fn().mockResolvedValue({ data: null, error: null });
|
|
132
|
+
|
|
133
|
+
const client = createMockClient({
|
|
134
|
+
auth: { signInWithSAML: signInWithSAMLMock },
|
|
135
|
+
} as any);
|
|
136
|
+
|
|
137
|
+
const { result } = renderHook(
|
|
138
|
+
() => useSignInWithSAML(),
|
|
139
|
+
{ wrapper: createWrapper(client) }
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
await act(async () => {
|
|
143
|
+
await result.current.mutateAsync({
|
|
144
|
+
provider: 'okta',
|
|
145
|
+
options: { redirectUrl: 'https://app.example.com' },
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(signInWithSAMLMock).toHaveBeenCalledWith('okta', {
|
|
150
|
+
redirectUrl: 'https://app.example.com',
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('useHandleSAMLCallback', () => {
|
|
156
|
+
it('should handle SAML callback successfully', async () => {
|
|
157
|
+
const mockResult = {
|
|
158
|
+
data: {
|
|
159
|
+
user: { id: '1', email: 'user@example.com' },
|
|
160
|
+
session: { access_token: 'token' },
|
|
161
|
+
},
|
|
162
|
+
error: null,
|
|
163
|
+
};
|
|
164
|
+
const handleSAMLCallbackMock = vi.fn().mockResolvedValue(mockResult);
|
|
165
|
+
|
|
166
|
+
const client = createMockClient({
|
|
167
|
+
auth: { handleSAMLCallback: handleSAMLCallbackMock },
|
|
168
|
+
} as any);
|
|
169
|
+
|
|
170
|
+
const { result } = renderHook(
|
|
171
|
+
() => useHandleSAMLCallback(),
|
|
172
|
+
{ wrapper: createWrapper(client) }
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
let response;
|
|
176
|
+
await act(async () => {
|
|
177
|
+
response = await result.current.mutateAsync({ samlResponse: 'base64-response' });
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
expect(handleSAMLCallbackMock).toHaveBeenCalledWith('base64-response', undefined);
|
|
181
|
+
expect(response).toEqual(mockResult);
|
|
182
|
+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should pass provider to callback handler', async () => {
|
|
186
|
+
const handleSAMLCallbackMock = vi.fn().mockResolvedValue({
|
|
187
|
+
data: { user: {}, session: {} },
|
|
188
|
+
error: null,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const client = createMockClient({
|
|
192
|
+
auth: { handleSAMLCallback: handleSAMLCallbackMock },
|
|
193
|
+
} as any);
|
|
194
|
+
|
|
195
|
+
const { result } = renderHook(
|
|
196
|
+
() => useHandleSAMLCallback(),
|
|
197
|
+
{ wrapper: createWrapper(client) }
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
await act(async () => {
|
|
201
|
+
await result.current.mutateAsync({
|
|
202
|
+
samlResponse: 'base64-response',
|
|
203
|
+
provider: 'okta',
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(handleSAMLCallbackMock).toHaveBeenCalledWith('base64-response', 'okta');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should not update cache when no data', async () => {
|
|
211
|
+
const handleSAMLCallbackMock = vi.fn().mockResolvedValue({
|
|
212
|
+
data: null,
|
|
213
|
+
error: null,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const client = createMockClient({
|
|
217
|
+
auth: { handleSAMLCallback: handleSAMLCallbackMock },
|
|
218
|
+
} as any);
|
|
219
|
+
|
|
220
|
+
const queryClient = createTestQueryClient();
|
|
221
|
+
const { result } = renderHook(
|
|
222
|
+
() => useHandleSAMLCallback(),
|
|
223
|
+
{ wrapper: createWrapper(client, queryClient) }
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
await act(async () => {
|
|
227
|
+
await result.current.mutateAsync({ samlResponse: 'base64-response' });
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Cache should not be updated
|
|
231
|
+
expect(queryClient.getQueryData(['fluxbase', 'auth', 'session'])).toBeUndefined();
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('useSAMLMetadataUrl', () => {
|
|
236
|
+
it('should return metadata URL generator function', () => {
|
|
237
|
+
const getSAMLMetadataUrlMock = vi.fn().mockReturnValue('https://api.example.com/saml/metadata/okta');
|
|
238
|
+
|
|
239
|
+
const client = createMockClient({
|
|
240
|
+
auth: { getSAMLMetadataUrl: getSAMLMetadataUrlMock },
|
|
241
|
+
} as any);
|
|
242
|
+
|
|
243
|
+
const { result } = renderHook(
|
|
244
|
+
() => useSAMLMetadataUrl(),
|
|
245
|
+
{ wrapper: createWrapper(client) }
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const url = result.current('okta');
|
|
249
|
+
|
|
250
|
+
expect(getSAMLMetadataUrlMock).toHaveBeenCalledWith('okta');
|
|
251
|
+
expect(url).toBe('https://api.example.com/saml/metadata/okta');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should work with different providers', () => {
|
|
255
|
+
const getSAMLMetadataUrlMock = vi.fn().mockImplementation((provider) => `https://api.example.com/saml/metadata/${provider}`);
|
|
256
|
+
|
|
257
|
+
const client = createMockClient({
|
|
258
|
+
auth: { getSAMLMetadataUrl: getSAMLMetadataUrlMock },
|
|
259
|
+
} as any);
|
|
260
|
+
|
|
261
|
+
const { result } = renderHook(
|
|
262
|
+
() => useSAMLMetadataUrl(),
|
|
263
|
+
{ wrapper: createWrapper(client) }
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
expect(result.current('okta')).toBe('https://api.example.com/saml/metadata/okta');
|
|
267
|
+
expect(result.current('azure')).toBe('https://api.example.com/saml/metadata/azure');
|
|
268
|
+
});
|
|
269
|
+
});
|
package/src/use-saml.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SAML SSO hooks for Fluxbase React SDK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
6
|
+
import { useFluxbaseClient } from "./context";
|
|
7
|
+
import type { SAMLLoginOptions, SAMLProvider } from "@fluxbase/sdk";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Hook to get available SAML SSO providers
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* function SAMLProviderList() {
|
|
15
|
+
* const { data: providers, isLoading } = useSAMLProviders()
|
|
16
|
+
*
|
|
17
|
+
* if (isLoading) return <div>Loading...</div>
|
|
18
|
+
*
|
|
19
|
+
* return (
|
|
20
|
+
* <div>
|
|
21
|
+
* {providers?.map(provider => (
|
|
22
|
+
* <button key={provider.id} onClick={() => signInWithSAML(provider.name)}>
|
|
23
|
+
* Sign in with {provider.name}
|
|
24
|
+
* </button>
|
|
25
|
+
* ))}
|
|
26
|
+
* </div>
|
|
27
|
+
* )
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function useSAMLProviders() {
|
|
32
|
+
const client = useFluxbaseClient();
|
|
33
|
+
|
|
34
|
+
return useQuery<SAMLProvider[]>({
|
|
35
|
+
queryKey: ["fluxbase", "auth", "saml", "providers"],
|
|
36
|
+
queryFn: async () => {
|
|
37
|
+
const { data, error } = await client.auth.getSAMLProviders();
|
|
38
|
+
if (error) throw error;
|
|
39
|
+
return data.providers;
|
|
40
|
+
},
|
|
41
|
+
staleTime: 1000 * 60 * 5, // 5 minutes - providers don't change often
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Hook to get SAML login URL for a provider
|
|
47
|
+
*
|
|
48
|
+
* This hook returns a function to get the login URL for a specific provider.
|
|
49
|
+
* Use this when you need more control over the redirect behavior.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```tsx
|
|
53
|
+
* function SAMLLoginButton({ provider }: { provider: string }) {
|
|
54
|
+
* const getSAMLLoginUrl = useGetSAMLLoginUrl()
|
|
55
|
+
*
|
|
56
|
+
* const handleClick = async () => {
|
|
57
|
+
* const { data, error } = await getSAMLLoginUrl.mutateAsync({
|
|
58
|
+
* provider,
|
|
59
|
+
* options: { redirectUrl: window.location.href }
|
|
60
|
+
* })
|
|
61
|
+
* if (!error) {
|
|
62
|
+
* window.location.href = data.url
|
|
63
|
+
* }
|
|
64
|
+
* }
|
|
65
|
+
*
|
|
66
|
+
* return <button onClick={handleClick}>Login with {provider}</button>
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function useGetSAMLLoginUrl() {
|
|
71
|
+
const client = useFluxbaseClient();
|
|
72
|
+
|
|
73
|
+
return useMutation({
|
|
74
|
+
mutationFn: async ({
|
|
75
|
+
provider,
|
|
76
|
+
options,
|
|
77
|
+
}: {
|
|
78
|
+
provider: string;
|
|
79
|
+
options?: SAMLLoginOptions;
|
|
80
|
+
}) => {
|
|
81
|
+
return await client.auth.getSAMLLoginUrl(provider, options);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Hook to initiate SAML login (redirects to IdP)
|
|
88
|
+
*
|
|
89
|
+
* This hook returns a mutation that when called, redirects the user to the
|
|
90
|
+
* SAML Identity Provider for authentication.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```tsx
|
|
94
|
+
* function SAMLLoginButton() {
|
|
95
|
+
* const signInWithSAML = useSignInWithSAML()
|
|
96
|
+
*
|
|
97
|
+
* return (
|
|
98
|
+
* <button
|
|
99
|
+
* onClick={() => signInWithSAML.mutate({ provider: 'okta' })}
|
|
100
|
+
* disabled={signInWithSAML.isPending}
|
|
101
|
+
* >
|
|
102
|
+
* {signInWithSAML.isPending ? 'Redirecting...' : 'Sign in with Okta'}
|
|
103
|
+
* </button>
|
|
104
|
+
* )
|
|
105
|
+
* }
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export function useSignInWithSAML() {
|
|
109
|
+
const client = useFluxbaseClient();
|
|
110
|
+
|
|
111
|
+
return useMutation({
|
|
112
|
+
mutationFn: async ({
|
|
113
|
+
provider,
|
|
114
|
+
options,
|
|
115
|
+
}: {
|
|
116
|
+
provider: string;
|
|
117
|
+
options?: SAMLLoginOptions;
|
|
118
|
+
}) => {
|
|
119
|
+
return await client.auth.signInWithSAML(provider, options);
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Hook to handle SAML callback after IdP authentication
|
|
126
|
+
*
|
|
127
|
+
* Use this in your SAML callback page to complete the authentication flow.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```tsx
|
|
131
|
+
* function SAMLCallbackPage() {
|
|
132
|
+
* const handleCallback = useHandleSAMLCallback()
|
|
133
|
+
* const navigate = useNavigate()
|
|
134
|
+
*
|
|
135
|
+
* useEffect(() => {
|
|
136
|
+
* const params = new URLSearchParams(window.location.search)
|
|
137
|
+
* const samlResponse = params.get('SAMLResponse')
|
|
138
|
+
*
|
|
139
|
+
* if (samlResponse) {
|
|
140
|
+
* handleCallback.mutate(
|
|
141
|
+
* { samlResponse },
|
|
142
|
+
* {
|
|
143
|
+
* onSuccess: () => navigate('/dashboard'),
|
|
144
|
+
* onError: (error) => console.error('SAML login failed:', error)
|
|
145
|
+
* }
|
|
146
|
+
* )
|
|
147
|
+
* }
|
|
148
|
+
* }, [])
|
|
149
|
+
*
|
|
150
|
+
* if (handleCallback.isPending) {
|
|
151
|
+
* return <div>Completing sign in...</div>
|
|
152
|
+
* }
|
|
153
|
+
*
|
|
154
|
+
* if (handleCallback.isError) {
|
|
155
|
+
* return <div>Authentication failed: {handleCallback.error.message}</div>
|
|
156
|
+
* }
|
|
157
|
+
*
|
|
158
|
+
* return null
|
|
159
|
+
* }
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
export function useHandleSAMLCallback() {
|
|
163
|
+
const client = useFluxbaseClient();
|
|
164
|
+
const queryClient = useQueryClient();
|
|
165
|
+
|
|
166
|
+
return useMutation({
|
|
167
|
+
mutationFn: async ({
|
|
168
|
+
samlResponse,
|
|
169
|
+
provider,
|
|
170
|
+
}: {
|
|
171
|
+
samlResponse: string;
|
|
172
|
+
provider?: string;
|
|
173
|
+
}) => {
|
|
174
|
+
return await client.auth.handleSAMLCallback(samlResponse, provider);
|
|
175
|
+
},
|
|
176
|
+
onSuccess: (result) => {
|
|
177
|
+
if (result.data) {
|
|
178
|
+
// Update auth state in React Query cache
|
|
179
|
+
queryClient.setQueryData(
|
|
180
|
+
["fluxbase", "auth", "session"],
|
|
181
|
+
result.data.session,
|
|
182
|
+
);
|
|
183
|
+
queryClient.setQueryData(
|
|
184
|
+
["fluxbase", "auth", "user"],
|
|
185
|
+
result.data.user,
|
|
186
|
+
);
|
|
187
|
+
// Invalidate any dependent queries
|
|
188
|
+
queryClient.invalidateQueries({ queryKey: ["fluxbase"] });
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Hook to get SAML Service Provider metadata URL
|
|
196
|
+
*
|
|
197
|
+
* Returns a function that generates the SP metadata URL for a given provider.
|
|
198
|
+
* Use this URL when configuring your SAML IdP.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```tsx
|
|
202
|
+
* function SAMLSetupInfo({ provider }: { provider: string }) {
|
|
203
|
+
* const getSAMLMetadataUrl = useSAMLMetadataUrl()
|
|
204
|
+
* const metadataUrl = getSAMLMetadataUrl(provider)
|
|
205
|
+
*
|
|
206
|
+
* return (
|
|
207
|
+
* <div>
|
|
208
|
+
* <p>SP Metadata URL:</p>
|
|
209
|
+
* <code>{metadataUrl}</code>
|
|
210
|
+
* </div>
|
|
211
|
+
* )
|
|
212
|
+
* }
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
export function useSAMLMetadataUrl() {
|
|
216
|
+
const client = useFluxbaseClient();
|
|
217
|
+
|
|
218
|
+
return (provider: string): string => {
|
|
219
|
+
return client.auth.getSAMLMetadataUrl(provider);
|
|
220
|
+
};
|
|
221
|
+
}
|