@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 +204 -1
- package/dist/components/AppsLogoSlider.d.ts +2 -1
- package/dist/components/NativeSSOPage.d.ts +10 -1
- package/dist/hooks/useNativeAuth.d.ts +7 -0
- package/dist/index.cjs +48 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +48 -32
- package/dist/index.js.map +1 -1
- package/dist/services/api.d.ts +8 -1
- package/package.json +1 -1
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
|
|
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 (
|
|
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
|
-
}, [
|
|
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 (
|
|
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
|
-
|
|
1738
|
+
setIsDebug(isDebugMode());
|
|
1739
|
+
if (isDebugMode()) console.log("✅ [Native] Credentials auto-refreshed");
|
|
1735
1740
|
} catch (err) {
|
|
1736
|
-
if (
|
|
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,
|
|
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
|
|
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
|
|
2989
|
-
return `${
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 }),
|