@ollaid/native-sso 1.0.2 → 1.0.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/README.md CHANGED
@@ -97,6 +97,7 @@ La page `/auth/sso` gère automatiquement :
97
97
  | `saasApiUrl` | `string` | ✅ | URL du backend SaaS (ex: `https://mon-saas.com/api`) |
98
98
  | `iamApiUrl` | `string` | ✅ | URL du backend IAM (ex: `https://identityam.ollaid.com/api`) |
99
99
  | `accountType` | `'user' \| 'client'` | ❌ | Type de compte à persister dans localStorage (défaut: `'user'`). Utile si vous avez plusieurs pages SSO avec des rôles différents. |
100
+ | `configPrefix` | `string` | ❌ | **Multi-tenant** : préfixe de configuration IAM côté backend (défaut: `'iam'`). Permet à un même backend SaaS de gérer N applications IAM. Voir [Multi-Tenant](#multi-tenant-plusieurs-applications-sur-le-même-backend). |
100
101
  | `onLoginSuccess` | `(token: string, user: UserInfos) => void` | ❌ | Callback après connexion réussie |
101
102
  | `onLogout` | `() => void` | ❌ | Callback après déconnexion |
102
103
  | `title` | `string` | ❌ | Titre personnalisé (défaut: "Un compte, plusieurs accès") |
@@ -105,7 +106,209 @@ La page `/auth/sso` gère automatiquement :
105
106
  | `hideFooter` | `boolean` | ❌ | Masquer "Propulsé par iam.ollaid.com" |
106
107
  | `onOnboardingComplete` | `(data: { image_url?: string; ccphone?: string; phone?: string }) => void` | ❌ | Callback après complétion de l'onboarding |
107
108
 
108
- > **Note :** Le mode `debug` est contrôlé **uniquement** par le backend via la variable d'environnement `IAM_DEBUG` dans le `.env` du SaaS. Il n'y a plus de prop `debug` à passer au composant.
109
+ > **Note :** Le mode `debug` est contrôlé **uniquement** par le backend via la variable d'environnement `IAM_DEBUG` dans le `.env` du SaaS. Il n'y a plus de prop `debug` à passer au composant. Le `DebugPanel` est **réactif** : il apparaît automatiquement après le chargement des credentials si `debug: true` est retourné par le backend.
110
+
111
+ ---
112
+
113
+ ## Multi-Tenant (plusieurs applications sur le même backend)
114
+
115
+ Le package supporte N applications IAM sur le même backend SaaS via le prop `configPrefix`. C'est dynamique : vous pouvez ajouter autant d'applications que nécessaire sans modifier le code du package.
116
+
117
+ ### Principe
118
+
119
+ Le frontend envoie un header `X-IAM-Config-Prefix` dans tous les appels au SaaS. Le backend utilise ce préfixe pour résoudre dynamiquement le bon bloc de configuration.
120
+
121
+ ```
122
+ Frontend (configPrefix="iam_vendor")
123
+ → GET /api/native/config [Header: X-IAM-Config-Prefix: iam_vendor]
124
+
125
+ Backend SaaS:
126
+ $prefix = $request->header('X-IAM-Config-Prefix', 'iam');
127
+ $appKey = config("services.{$prefix}.app_key"); // ← résolution dynamique
128
+ ```
129
+
130
+ ### Côté Frontend
131
+
132
+ ```tsx
133
+ {/* Page login principale */}
134
+ <NativeSSOPage
135
+ saasApiUrl="https://votre-saas.com/api"
136
+ iamApiUrl="https://identityam.ollaid.com/api"
137
+ configPrefix="iam"
138
+ accountType="user"
139
+ />
140
+
141
+ {/* Page login espace vendeur */}
142
+ <NativeSSOPage
143
+ saasApiUrl="https://votre-saas.com/api"
144
+ iamApiUrl="https://identityam.ollaid.com/api"
145
+ configPrefix="iam_vendor"
146
+ accountType="client"
147
+ />
148
+
149
+ {/* Page login admin — même backend, app IAM différente */}
150
+ <NativeSSOPage
151
+ saasApiUrl="https://votre-saas.com/api"
152
+ iamApiUrl="https://identityam.ollaid.com/api"
153
+ configPrefix="iam_admin"
154
+ accountType="user"
155
+ />
156
+ ```
157
+
158
+ ### Côté Backend SaaS — `.env`
159
+
160
+ Le préfixe `.env` correspond au `configPrefix` en UPPER_CASE. Ajoutez autant de blocs que nécessaire :
161
+
162
+ ```env
163
+ # ===== Serveur IAM (partagé) =====
164
+ IAM_API_URL=https://identityam.ollaid.com/api
165
+ IAM_AUTH_URL=https://iam.ollaid.com
166
+
167
+ # ===== Préfixe "iam" (application principale) =====
168
+ IAM_APP_KEY=oiam_ak_xxx
169
+ IAM_PUBLIC_KEY=oiam_pk_xxx
170
+ IAM_SECRET_KEY=oiam_sk_xxx
171
+ IAM_WEBHOOK_SECRET=oiam_whsec_xxx
172
+ IAM_DEBUG=true
173
+
174
+ # ===== Préfixe "iam_vendor" (espace vendeur/shop) =====
175
+ IAM_VENDOR_APP_KEY=oiam_ak_yyy
176
+ IAM_VENDOR_PUBLIC_KEY=oiam_pk_yyy
177
+ IAM_VENDOR_SECRET_KEY=oiam_sk_yyy
178
+ IAM_VENDOR_WEBHOOK_SECRET=oiam_whsec_yyy
179
+ IAM_VENDOR_DEBUG=false
180
+
181
+ # ===== Préfixe "iam_client" (espace client) =====
182
+ IAM_CLIENT_APP_KEY=oiam_ak_zzz
183
+ IAM_CLIENT_SECRET_KEY=oiam_sk_zzz
184
+ IAM_CLIENT_DEBUG=true
185
+
186
+ # ===== Préfixe "iam_admin" (back-office) =====
187
+ IAM_ADMIN_APP_KEY=oiam_ak_aaa
188
+ IAM_ADMIN_SECRET_KEY=oiam_sk_aaa
189
+ IAM_ADMIN_DEBUG=true
190
+ ```
191
+
192
+ ### Côté Backend SaaS — `config/services.php`
193
+
194
+ ```php
195
+ return [
196
+ 'iam' => [
197
+ 'api_url' => env('IAM_API_URL', 'https://identityam.ollaid.com/api'),
198
+ 'app_key' => env('IAM_APP_KEY'),
199
+ 'public_key' => env('IAM_PUBLIC_KEY'),
200
+ 'secret_key' => env('IAM_SECRET_KEY'),
201
+ 'debug' => env('IAM_DEBUG', false),
202
+ ],
203
+ 'iam_vendor' => [
204
+ 'api_url' => env('IAM_API_URL'),
205
+ 'app_key' => env('IAM_VENDOR_APP_KEY'),
206
+ 'public_key' => env('IAM_VENDOR_PUBLIC_KEY'),
207
+ 'secret_key' => env('IAM_VENDOR_SECRET_KEY'),
208
+ 'debug' => env('IAM_VENDOR_DEBUG', false),
209
+ ],
210
+ 'iam_client' => [
211
+ 'api_url' => env('IAM_API_URL'),
212
+ 'app_key' => env('IAM_CLIENT_APP_KEY'),
213
+ 'secret_key' => env('IAM_CLIENT_SECRET_KEY'),
214
+ 'debug' => env('IAM_CLIENT_DEBUG', false),
215
+ ],
216
+ 'iam_admin' => [
217
+ 'api_url' => env('IAM_API_URL'),
218
+ 'app_key' => env('IAM_ADMIN_APP_KEY'),
219
+ 'secret_key' => env('IAM_ADMIN_SECRET_KEY'),
220
+ 'debug' => env('IAM_ADMIN_DEBUG', false),
221
+ ],
222
+ // Ajoutez d'autres blocs selon vos besoins...
223
+ ];
224
+ ```
225
+
226
+ ### Côté Backend SaaS — Controller multi-tenant
227
+
228
+ Tous les controllers Native (`config`, `exchange`, `check-token`, `logout`) doivent lire le header :
229
+
230
+ ```php
231
+ class NativeConfigController extends Controller
232
+ {
233
+ public function getConfig(Request $request): JsonResponse
234
+ {
235
+ // Multi-tenant : résolution dynamique du préfixe
236
+ $prefix = $request->header('X-IAM-Config-Prefix', 'iam');
237
+
238
+ // Sécurité : valider que le préfixe commence par "iam"
239
+ if (!str_starts_with($prefix, 'iam')) {
240
+ return response()->json(['success' => false, 'message' => 'Invalid config prefix'], 400);
241
+ }
242
+
243
+ $appKey = config("services.{$prefix}.app_key");
244
+ $secretKey = config("services.{$prefix}.secret_key");
245
+ $debug = (bool) config("services.{$prefix}.debug", false);
246
+
247
+ if (!$appKey || !$secretKey) {
248
+ return response()->json([
249
+ 'success' => false,
250
+ 'message' => "Configuration '{$prefix}' non trouvée",
251
+ ], 404);
252
+ }
253
+
254
+ // Chiffrement Opaque Token (AES-256-CBC)
255
+ $payload = json_encode(['secret_key' => $secretKey, 'ts' => time()]);
256
+ $key = hash('sha256', $secretKey, true);
257
+ $iv = random_bytes(16);
258
+ $encrypted = openssl_encrypt($payload, 'AES-256-CBC', $key, 0, $iv);
259
+ $encryptedCredentials = base64_encode($iv . '::' . $encrypted);
260
+
261
+ return response()->json([
262
+ 'success' => true,
263
+ 'app_key' => $appKey,
264
+ 'encrypted_credentials' => $encryptedCredentials,
265
+ 'credentials_ttl' => 300,
266
+ 'debug' => $debug,
267
+ ]);
268
+ }
269
+ }
270
+ ```
271
+
272
+ > **⚠️ Important** : Appliquez la même logique `X-IAM-Config-Prefix` dans `exchange`, `check-token` et `logout`.
273
+
274
+ ---
275
+
276
+ ## Debug Mode — Troubleshooting
277
+
278
+ Le debug est **piloté par le backend** : le package n'a aucun prop `debug`.
279
+
280
+ ### Comment ça marche
281
+
282
+ 1. Le backend lit `IAM_DEBUG` (ou `IAM_VENDOR_DEBUG`, etc.) depuis `.env`
283
+ 2. `GET /api/native/config` retourne `"debug": true`
284
+ 3. Le package active les logs console + le `DebugPanel`
285
+ 4. Le `DebugPanel` est **réactif** : il apparaît automatiquement après le chargement des credentials
286
+
287
+ ### Checklist de troubleshooting
288
+
289
+ | Problème | Solution |
290
+ |----------|----------|
291
+ | `DebugPanel` n'apparaît pas | Vérifier que `GET /api/native/config` retourne `"debug": true` dans la réponse JSON |
292
+ | La valeur est toujours `false` | Vérifier le `.env` : pas de typo ! (`IAM_DEBUG` et non `IAM_DEBUGL`) |
293
+ | Changement non pris en compte | Lancer `php artisan config:clear && php artisan config:cache` |
294
+ | Debug actif en production | Mettre `IAM_DEBUG=false` dans le `.env` de production |
295
+ | Debug marche pour main mais pas vendor | Vérifier `IAM_VENDOR_DEBUG=true` dans `.env` + `config/services.php` |
296
+
297
+ ### ⚠️ Erreur fréquente : typo dans `.env`
298
+
299
+ ```env
300
+ # ❌ MAUVAIS (typo "DEBUGL" avec un L en trop)
301
+ IAM_DEBUGL=true
302
+
303
+ # ✅ CORRECT
304
+ IAM_DEBUG=true
305
+ ```
306
+
307
+ Après correction, n'oubliez pas :
308
+ ```bash
309
+ php artisan config:clear
310
+ php artisan config:cache
311
+ ```
109
312
 
110
313
  ---
111
314
 
@@ -4,6 +4,7 @@
4
4
  interface AppsLogoSliderProps {
5
5
  speed?: 'slow' | 'normal' | 'fast';
6
6
  className?: string;
7
+ iamApiUrl?: string;
7
8
  }
8
- export declare function AppsLogoSlider({ speed, className }: AppsLogoSliderProps): import("react/jsx-runtime").JSX.Element | null;
9
+ export declare function AppsLogoSlider({ speed, className, iamApiUrl: iamApiUrlProp }: AppsLogoSliderProps): import("react/jsx-runtime").JSX.Element | null;
9
10
  export default AppsLogoSlider;
@@ -22,6 +22,11 @@ export interface NativeSSOPageProps {
22
22
  }) => void;
23
23
  /** Type de compte à persister dans localStorage (défaut: 'user') */
24
24
  accountType?: 'user' | 'client';
25
+ /**
26
+ * Préfixe de configuration IAM côté backend (défaut: 'iam').
27
+ * Permet le multi-tenant : 'iam', 'iam_vendor', 'iam_client', 'iam_admin', etc.
28
+ */
29
+ configPrefix?: string;
25
30
  /** Titre personnalisé */
26
31
  title?: string;
27
32
  /** Description personnalisée */
@@ -30,6 +35,10 @@ export interface NativeSSOPageProps {
30
35
  logoUrl?: string;
31
36
  /** Masquer le footer "Propulsé par" */
32
37
  hideFooter?: boolean;
38
+ /** Route vers laquelle rediriger après un login réussi (via window.location.href) */
39
+ redirectAfterLogin?: string;
40
+ /** Route vers laquelle rediriger après un logout (via window.location.href) */
41
+ redirectAfterLogout?: string;
33
42
  }
34
- export declare function NativeSSOPage({ saasApiUrl, iamApiUrl, onLoginSuccess, onLogout, onOnboardingComplete, accountType, title, description, logoUrl, hideFooter, }: NativeSSOPageProps): import("react/jsx-runtime").JSX.Element;
43
+ export declare function NativeSSOPage({ saasApiUrl, iamApiUrl, onLoginSuccess, onLogout, onOnboardingComplete, accountType, configPrefix, title, description, logoUrl, hideFooter, redirectAfterLogin, redirectAfterLogout, }: NativeSSOPageProps): import("react/jsx-runtime").JSX.Element;
35
44
  export default NativeSSOPage;
@@ -14,6 +14,11 @@ export interface UseNativeAuthOptions {
14
14
  autoLoadCredentials?: boolean;
15
15
  /** Type de compte par défaut à persister (défaut: 'user') */
16
16
  defaultAccountType?: 'user' | 'client';
17
+ /**
18
+ * Préfixe de configuration IAM côté backend (défaut: 'iam').
19
+ * Permet le multi-tenant dynamique : 'iam', 'iam_vendor', 'iam_client', etc.
20
+ */
21
+ configPrefix?: string;
17
22
  }
18
23
  export declare function useNativeAuth(options: UseNativeAuthOptions): {
19
24
  credentialsLoaded: boolean;
@@ -49,6 +54,8 @@ export declare function useNativeAuth(options: UseNativeAuthOptions): {
49
54
  error: string | null;
50
55
  errorType: string | null;
51
56
  isAuthenticated: boolean;
57
+ /** Debug réactif — mis à jour après loadCredentials */
58
+ isDebug: boolean;
52
59
  accountType: AccountType;
53
60
  isPhoneOnly: boolean;
54
61
  otpMethod: "email" | "sms" | null | undefined;
package/dist/index.cjs CHANGED
@@ -454,7 +454,8 @@ let config = {
454
454
  saasApiUrl: "",
455
455
  iamApiUrl: "",
456
456
  timeout: 3e4,
457
- debug: false
457
+ debug: false,
458
+ configPrefix: "iam"
458
459
  };
459
460
  const setNativeAuthConfig = (newConfig) => {
460
461
  config = { ...config, ...newConfig };
@@ -574,7 +575,7 @@ async function fetchWithTimeout(url, options, timeout) {
574
575
  throw new ApiError("Erreur inattendue", "unknown");
575
576
  }
576
577
  }
577
- function getHeaders(token) {
578
+ function getHeaders(token, includeConfigPrefix = false) {
578
579
  const headers = {
579
580
  "Content-Type": "application/json",
580
581
  "Accept": "application/json"
@@ -583,6 +584,9 @@ function getHeaders(token) {
583
584
  if (deviceId) {
584
585
  headers["X-Device-Id"] = deviceId;
585
586
  }
587
+ if (includeConfigPrefix && config.configPrefix) {
588
+ headers["X-IAM-Config-Prefix"] = config.configPrefix;
589
+ }
586
590
  if (token) {
587
591
  headers["Authorization"] = `Bearer ${token}`;
588
592
  }
@@ -630,7 +634,7 @@ const nativeAuthService = {
630
634
  }
631
635
  const response = await fetchWithTimeout(
632
636
  `${config2.saasApiUrl}/native/config`,
633
- { method: "GET", headers: getHeaders() },
637
+ { method: "GET", headers: getHeaders(void 0, true) },
634
638
  config2.timeout || 3e4
635
639
  );
636
640
  if (!response.success || !response.encrypted_credentials || !response.app_key) {
@@ -805,7 +809,7 @@ const nativeAuthService = {
805
809
  `${config2.saasApiUrl}/native/exchange`,
806
810
  {
807
811
  method: "POST",
808
- headers: getHeaders(),
812
+ headers: getHeaders(void 0, true),
809
813
  body: JSON.stringify({ callback_token: callbackToken })
810
814
  },
811
815
  config2.timeout || 3e4
@@ -834,7 +838,7 @@ const nativeAuthService = {
834
838
  `${cfg.saasApiUrl}/native/check-token`,
835
839
  {
836
840
  method: "POST",
837
- headers: getHeaders(token)
841
+ headers: getHeaders(token, true)
838
842
  },
839
843
  1e4
840
844
  );
@@ -856,7 +860,7 @@ const nativeAuthService = {
856
860
  `${config2.saasApiUrl}/native/logout`,
857
861
  {
858
862
  method: "POST",
859
- headers: getHeaders(token)
863
+ headers: getHeaders(token, true)
860
864
  },
861
865
  config2.timeout || 3e4
862
866
  );
@@ -1641,15 +1645,15 @@ function getErrorMessage$1(err, context) {
1641
1645
  return { message: `Erreur lors de ${context}`, type: "unknown" };
1642
1646
  }
1643
1647
  function useNativeAuth(options) {
1644
- const { saasApiUrl, iamApiUrl, autoLoadCredentials = true, defaultAccountType = "user" } = options;
1648
+ const { saasApiUrl, iamApiUrl, autoLoadCredentials = true, defaultAccountType = "user", configPrefix = "iam" } = options;
1645
1649
  const configuredRef = react.useRef(false);
1646
- const debug = isDebugMode();
1650
+ const [isDebug, setIsDebug] = react.useState(isDebugMode());
1647
1651
  react.useEffect(() => {
1648
1652
  if (!configuredRef.current) {
1649
- setNativeAuthConfig({ saasApiUrl, iamApiUrl });
1653
+ setNativeAuthConfig({ saasApiUrl, iamApiUrl, configPrefix });
1650
1654
  configuredRef.current = true;
1651
1655
  }
1652
- }, [saasApiUrl, iamApiUrl]);
1656
+ }, [saasApiUrl, iamApiUrl, configPrefix]);
1653
1657
  const [state, setState] = react.useState({
1654
1658
  credentialsLoaded: false,
1655
1659
  processToken: null,
@@ -1667,7 +1671,7 @@ function useNativeAuth(options) {
1667
1671
  });
1668
1672
  const [accountType, setAccountType] = react.useState("email");
1669
1673
  const handleTokenInvalid = react.useCallback(() => {
1670
- if (debug) console.log("🔐 [HealthCheck] Token invalide — déconnexion locale");
1674
+ if (isDebug) console.log("🔐 [HealthCheck] Token invalide — déconnexion locale");
1671
1675
  clearSession();
1672
1676
  setState({
1673
1677
  credentialsLoaded: false,
@@ -1684,7 +1688,7 @@ function useNativeAuth(options) {
1684
1688
  otpMethod: null,
1685
1689
  otpSentTo: null
1686
1690
  });
1687
- }, [debug]);
1691
+ }, [isDebug]);
1688
1692
  const handleUserUpdated = react.useCallback((userInfos) => {
1689
1693
  const storedRaw = localStorage.getItem(STORAGE.USER);
1690
1694
  if (storedRaw) {
@@ -1702,7 +1706,7 @@ function useNativeAuth(options) {
1702
1706
  saasApiUrl,
1703
1707
  onTokenInvalid: handleTokenInvalid,
1704
1708
  onUserUpdated: handleUserUpdated,
1705
- debug
1709
+ debug: isDebug
1706
1710
  });
1707
1711
  react.useEffect(() => {
1708
1712
  const storedToken = localStorage.getItem(STORAGE.AUTH_TOKEN) || localStorage.getItem(STORAGE.TOKEN);
@@ -1725,23 +1729,25 @@ function useNativeAuth(options) {
1725
1729
  if (state.status === "completed" || !state.credentialsLoaded) return;
1726
1730
  const ttl = nativeAuthService.getCredentialsTtl();
1727
1731
  const refreshInterval = Math.max((ttl - 30) * 1e3, 3e4);
1728
- if (debug) {
1732
+ if (isDebug) {
1729
1733
  console.log(`🔄 [Native] Auto-refresh credentials every ${refreshInterval / 1e3}s (TTL: ${ttl}s)`);
1730
1734
  }
1731
1735
  const timer = setInterval(async () => {
1732
1736
  try {
1733
1737
  await nativeAuthService.loadCredentials();
1734
- if (debug) console.log("✅ [Native] Credentials auto-refreshed");
1738
+ setIsDebug(isDebugMode());
1739
+ if (isDebugMode()) console.log("✅ [Native] Credentials auto-refreshed");
1735
1740
  } catch (err) {
1736
- if (debug) console.warn("⚠️ [Native] Auto-refresh failed:", err);
1741
+ if (isDebugMode()) console.warn("⚠️ [Native] Auto-refresh failed:", err);
1737
1742
  }
1738
1743
  }, refreshInterval);
1739
1744
  return () => clearInterval(timer);
1740
- }, [state.status, state.credentialsLoaded, debug]);
1745
+ }, [state.status, state.credentialsLoaded, isDebug]);
1741
1746
  const loadCredentials = react.useCallback(async () => {
1742
1747
  setState((prev) => ({ ...prev, loading: true, error: null }));
1743
1748
  try {
1744
1749
  await nativeAuthService.loadCredentials();
1750
+ setIsDebug(isDebugMode());
1745
1751
  setState((prev) => ({ ...prev, credentialsLoaded: true, loading: false }));
1746
1752
  return { success: true };
1747
1753
  } catch (err) {
@@ -2229,6 +2235,8 @@ function useNativeAuth(options) {
2229
2235
  error: state.error,
2230
2236
  errorType: state.errorType,
2231
2237
  isAuthenticated: state.status === "completed" && state.user !== null,
2238
+ /** Debug réactif — mis à jour après loadCredentials */
2239
+ isDebug,
2232
2240
  accountType,
2233
2241
  isPhoneOnly: accountType === "phone-only",
2234
2242
  otpMethod: state.otpMethod,
@@ -2946,17 +2954,18 @@ function PhoneInput({
2946
2954
  ] })
2947
2955
  ] });
2948
2956
  }
2949
- function AppsLogoSlider({ speed = "normal", className = "" }) {
2957
+ function AppsLogoSlider({ speed = "normal", className = "", iamApiUrl: iamApiUrlProp }) {
2950
2958
  const scrollRef = react.useRef(null);
2951
2959
  const [isPaused, setIsPaused] = react.useState(false);
2952
2960
  const [applications, setApplications] = react.useState([]);
2953
2961
  const [isLoading, setIsLoading] = react.useState(true);
2954
2962
  const speedMap = { slow: 50, normal: 30, fast: 15 };
2955
2963
  react.useEffect(() => {
2964
+ const resolvedIamApiUrl = iamApiUrlProp || getNativeAuthConfig().iamApiUrl;
2965
+ if (!resolvedIamApiUrl) return;
2956
2966
  const fetchApps = async () => {
2957
2967
  try {
2958
- const config2 = getNativeAuthConfig();
2959
- const iamBaseUrl = config2.iamApiUrl.replace("/api", "");
2968
+ const iamBaseUrl = resolvedIamApiUrl.replace("/api", "");
2960
2969
  const response = await fetch(`${iamBaseUrl}/api/public/applications`, { headers: { "Accept": "application/json" } });
2961
2970
  if (response.ok) {
2962
2971
  const data = await response.json();
@@ -2968,7 +2977,7 @@ function AppsLogoSlider({ speed = "normal", className = "" }) {
2968
2977
  }
2969
2978
  };
2970
2979
  fetchApps();
2971
- }, []);
2980
+ }, [iamApiUrlProp]);
2972
2981
  react.useEffect(() => {
2973
2982
  const container = scrollRef.current;
2974
2983
  if (!container || applications.length === 0) return;
@@ -2985,8 +2994,8 @@ function AppsLogoSlider({ speed = "normal", className = "" }) {
2985
2994
  const getLogoUrl = (logo) => {
2986
2995
  if (!logo) return null;
2987
2996
  if (logo.startsWith("http")) return logo;
2988
- const config2 = getNativeAuthConfig();
2989
- return `${config2.iamApiUrl.replace("/api", "")}/storage/applications/${logo}`;
2997
+ const resolvedUrl = iamApiUrlProp || getNativeAuthConfig().iamApiUrl;
2998
+ return `${resolvedUrl.replace("/api", "")}/storage/applications/${logo}`;
2990
2999
  };
2991
3000
  if (isLoading) {
2992
3001
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: { overflow: "hidden" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: "1rem", padding: "0.5rem 0" }, children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flexShrink: 0, display: "flex", flexDirection: "column", alignItems: "center", gap: "0.5rem" }, children: [
@@ -3581,7 +3590,7 @@ function SignupModal({ open, onOpenChange, onSwitchToLogin, onSignupSuccess, saa
3581
3590
  /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: "Ouvrez un compte Ollaid" }),
3582
3591
  /* @__PURE__ */ jsxRuntime.jsx(DialogDescription, { children: "Un compte unique qui vous donne accès à toutes les applications" })
3583
3592
  ] }),
3584
- /* @__PURE__ */ jsxRuntime.jsx(AppsLogoSlider, {}),
3593
+ /* @__PURE__ */ jsxRuntime.jsx(AppsLogoSlider, { iamApiUrl }),
3585
3594
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem", fontSize: "0.875rem", color: C$1.gray500, margin: "1rem 0" }, children: ["Un seul compte pour toutes les applications", "Plus besoin de multiples mots de passe", "Connexion simplifiée et sécurisée"].map((text) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.75rem" }, children: [
3586
3595
  /* @__PURE__ */ jsxRuntime.jsx(IconCheckCircle2, { style: { width: "1.25rem", height: "1.25rem", color: C$1.green, flexShrink: 0 } }),
3587
3596
  text
@@ -4136,10 +4145,13 @@ function NativeSSOPage({
4136
4145
  onLogout,
4137
4146
  onOnboardingComplete,
4138
4147
  accountType = "user",
4148
+ configPrefix = "iam",
4139
4149
  title = "Un compte, plusieurs accès",
4140
4150
  description = "Connectez-vous avec votre compte Ollaid pour accéder à toutes les applications partenaires.",
4141
4151
  logoUrl,
4142
- hideFooter = false
4152
+ hideFooter = false,
4153
+ redirectAfterLogin,
4154
+ redirectAfterLogout
4143
4155
  }) {
4144
4156
  const [modal, setModal] = react.useState("none");
4145
4157
  const [showOnboarding, setShowOnboarding] = react.useState(false);
@@ -4153,7 +4165,7 @@ function NativeSSOPage({
4153
4165
  }
4154
4166
  return null;
4155
4167
  });
4156
- const resolvedDebug = isDebugMode();
4168
+ const { isDebug: resolvedDebug } = useNativeAuth({ saasApiUrl, iamApiUrl, configPrefix, autoLoadCredentials: true });
4157
4169
  react.useEffect(() => {
4158
4170
  const root = document.documentElement;
4159
4171
  const originalValues = {};
@@ -4208,8 +4220,9 @@ function NativeSSOPage({
4208
4220
  } else {
4209
4221
  setSession({ token, user: userObj });
4210
4222
  onLoginSuccess == null ? void 0 : onLoginSuccess(token, user);
4223
+ if (redirectAfterLogin) window.location.href = redirectAfterLogin;
4211
4224
  }
4212
- }, [onLoginSuccess]);
4225
+ }, [onLoginSuccess, redirectAfterLogin]);
4213
4226
  const handleOnboardingComplete = react.useCallback((data) => {
4214
4227
  if (!pendingSession) return;
4215
4228
  const updatedUser = { ...pendingSession.user, ...data };
@@ -4219,14 +4232,16 @@ function NativeSSOPage({
4219
4232
  setPendingSession(null);
4220
4233
  onOnboardingComplete == null ? void 0 : onOnboardingComplete(data);
4221
4234
  onLoginSuccess == null ? void 0 : onLoginSuccess(pendingSession.token, updatedUser);
4222
- }, [pendingSession, onLoginSuccess, onOnboardingComplete]);
4235
+ if (redirectAfterLogin) window.location.href = redirectAfterLogin;
4236
+ }, [pendingSession, onLoginSuccess, onOnboardingComplete, redirectAfterLogin]);
4223
4237
  const handleOnboardingSkip = react.useCallback(() => {
4224
4238
  if (!pendingSession) return;
4225
4239
  setShowOnboarding(false);
4226
4240
  setSession(pendingSession);
4227
4241
  setPendingSession(null);
4228
4242
  onLoginSuccess == null ? void 0 : onLoginSuccess(pendingSession.token, pendingSession.user);
4229
- }, [pendingSession, onLoginSuccess]);
4243
+ if (redirectAfterLogin) window.location.href = redirectAfterLogin;
4244
+ }, [pendingSession, onLoginSuccess, redirectAfterLogin]);
4230
4245
  const handleLogout = react.useCallback(() => {
4231
4246
  localStorage.removeItem(STORAGE.AUTH_TOKEN);
4232
4247
  localStorage.removeItem(STORAGE.TOKEN);
@@ -4235,7 +4250,8 @@ function NativeSSOPage({
4235
4250
  localStorage.removeItem(STORAGE.ALIAS_REFERENCE);
4236
4251
  setSession(null);
4237
4252
  onLogout == null ? void 0 : onLogout();
4238
- }, [onLogout]);
4253
+ if (redirectAfterLogout) window.location.href = redirectAfterLogout;
4254
+ }, [onLogout, redirectAfterLogout]);
4239
4255
  const containerStyle = {
4240
4256
  minHeight: "100vh",
4241
4257
  backgroundColor: COLORS.primary,
@@ -4270,7 +4286,7 @@ function NativeSSOPage({
4270
4286
  ] }) });
4271
4287
  if (session) {
4272
4288
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: containerStyle, children: [
4273
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: "100%", maxWidth: "28rem", marginBottom: "1.5rem" }, children: /* @__PURE__ */ jsxRuntime.jsx(AppsLogoSlider, {}) }),
4289
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: "100%", maxWidth: "28rem", marginBottom: "1.5rem" }, children: /* @__PURE__ */ jsxRuntime.jsx(AppsLogoSlider, { iamApiUrl }) }),
4274
4290
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "2rem 1.5rem 1.5rem" }, children: [
4275
4291
  /* @__PURE__ */ jsxRuntime.jsx(BrandingHeader, {}),
4276
4292
  /* @__PURE__ */ jsxRuntime.jsxs("h2", { style: { fontSize: "1.25rem", fontWeight: 600, textAlign: "center", color: COLORS.cardForeground }, children: [
@@ -4290,7 +4306,7 @@ function NativeSSOPage({
4290
4306
  ] });
4291
4307
  }
4292
4308
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: containerStyle, children: [
4293
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: "100%", maxWidth: "28rem", marginBottom: "1.5rem" }, children: logoUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoUrl, alt: "Logo", style: { height: "3rem", margin: "0 auto" } }) : /* @__PURE__ */ jsxRuntime.jsx(AppsLogoSlider, {}) }),
4309
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: "100%", maxWidth: "28rem", marginBottom: "1.5rem" }, children: logoUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoUrl, alt: "Logo", style: { height: "3rem", margin: "0 auto" } }) : /* @__PURE__ */ jsxRuntime.jsx(AppsLogoSlider, { iamApiUrl }) }),
4294
4310
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "2rem 1.5rem 1.5rem" }, children: [
4295
4311
  /* @__PURE__ */ jsxRuntime.jsx(BrandingHeader, {}),
4296
4312
  /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { fontSize: "1.25rem", fontWeight: 600, textAlign: "center", color: COLORS.cardForeground, marginBottom: "0.5rem" }, children: title }),