@ollaid/native-sso 2.1.3 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +202 -55
- package/dist/components/AvatarCropModal.d.ts +16 -0
- package/dist/components/DebugPanel.d.ts +7 -2
- package/dist/components/LoginModal.d.ts +2 -2
- package/dist/components/NativeSSOPage.d.ts +8 -2
- package/dist/components/OnboardingModal.d.ts +14 -7
- package/dist/components/PasswordRecoveryModal.d.ts +1 -1
- package/dist/components/PhoneInput.d.ts +2 -1
- package/dist/components/SignupModal.d.ts +1 -1
- package/dist/components/ui.d.ts +4 -2
- package/dist/hooks/useLogout.d.ts +1 -1
- package/dist/hooks/useMobilePassword.d.ts +1 -1
- package/dist/hooks/useMobileRegistration.d.ts +1 -1
- package/dist/hooks/useNativeAuth.d.ts +1 -1
- package/dist/hooks/useTokenHealthCheck.d.ts +11 -1
- package/dist/index.cjs +2075 -202
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +2076 -203
- package/dist/index.js.map +1 -1
- package/dist/provider.d.ts +1 -1
- package/dist/services/api.d.ts +28 -1
- package/dist/services/debugLogger.d.ts +1 -1
- package/dist/services/iamAccount.d.ts +1 -1
- package/dist/services/mobilePassword.d.ts +1 -1
- package/dist/services/mobileRegistration.d.ts +1 -1
- package/dist/services/nativeAuth.d.ts +3 -2
- package/dist/services/profile.d.ts +31 -0
- package/dist/services/profileChange.d.ts +30 -0
- package/dist/services/profileMedia.d.ts +16 -0
- package/dist/types/mobile.d.ts +1 -1
- package/dist/types/native.d.ts +23 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@ Package NPM Frontend-First pour l'authentification Native SSO Ollaid.
|
|
|
28
28
|
19. [Sécurité](#sécurité)
|
|
29
29
|
20. [Exports](#exports)
|
|
30
30
|
21. [Publication & Installation npm](#publication--installation-npm)
|
|
31
|
+
22. [Webhooks & Health Check (Backend)](#webhooks--health-check-backend)
|
|
31
32
|
|
|
32
33
|
---
|
|
33
34
|
|
|
@@ -59,7 +60,7 @@ function App() {
|
|
|
59
60
|
return (
|
|
60
61
|
<Routes>
|
|
61
62
|
<Route
|
|
62
|
-
path="/auth/sso"
|
|
63
|
+
path="/auth/native-sso"
|
|
63
64
|
element={
|
|
64
65
|
<NativeSSOPage
|
|
65
66
|
saasApiUrl="https://mon-saas.com/api"
|
|
@@ -69,7 +70,7 @@ function App() {
|
|
|
69
70
|
console.log('Connecté !', user.name);
|
|
70
71
|
navigate('/dashboard');
|
|
71
72
|
}}
|
|
72
|
-
onLogout={() => navigate('/auth/sso')}
|
|
73
|
+
onLogout={() => navigate('/auth/native-sso')}
|
|
73
74
|
/>
|
|
74
75
|
}
|
|
75
76
|
/>
|
|
@@ -81,7 +82,7 @@ function App() {
|
|
|
81
82
|
|
|
82
83
|
### 3. C'est tout ✅
|
|
83
84
|
|
|
84
|
-
La page `/auth/sso` gère automatiquement :
|
|
85
|
+
La page `/auth/native-sso` gère automatiquement :
|
|
85
86
|
- ✅ Connexion par email (mot de passe + OTP)
|
|
86
87
|
- ✅ Connexion par téléphone (SMS OTP)
|
|
87
88
|
- ✅ Connexion par code d'accès
|
|
@@ -104,15 +105,16 @@ La page `/auth/sso` gère automatiquement :
|
|
|
104
105
|
| `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). |
|
|
105
106
|
| `onLoginSuccess` | `(token: string, user: UserInfos) => void` | ❌ | Callback après connexion réussie |
|
|
106
107
|
| `onLogout` | `() => void` | ❌ | Callback après déconnexion |
|
|
107
|
-
| `title` | `string` | ❌ | Titre personnalisé (défaut: "Un compte
|
|
108
|
+
| `title` | `string` | ❌ | Titre personnalisé (défaut: "Un compte pour toutes vos applications") |
|
|
108
109
|
| `description` | `string` | ❌ | Description personnalisée |
|
|
109
110
|
| `logoUrl` | `string` | ❌ | URL du logo (remplace le slider) |
|
|
110
111
|
| `hideFooter` | `boolean` | ❌ | Masquer "Propulsé par iam.ollaid.com" |
|
|
111
|
-
| `onOnboardingComplete` | `(data: { image_url?: string; ccphone?: string; phone?: string }) => void` | ❌ | Callback après complétion de l'onboarding |
|
|
112
|
+
| `onOnboardingComplete` | `(data: { name?: string; image_url?: string; ccphone?: string; phone?: string; email?: string }) => void` | ❌ | Callback après complétion de l'onboarding |
|
|
112
113
|
| `redirectAfterLogin` | `string` | ❌ | Route vers laquelle rediriger après connexion réussie (ex: `/client/dashboard`). Utilise `window.location.href`. Compatible avec ou sans react-router. |
|
|
113
114
|
| `redirectAfterLogout` | `string` | ❌ | Route vers laquelle rediriger après déconnexion (ex: `/auth/client`). Utilise `window.location.href`. |
|
|
114
115
|
|
|
115
116
|
> **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.
|
|
117
|
+
> Quand le `DebugPanel` est visible, il expose aussi des boutons de test pour ouvrir `Connexion`, `Inscription`, `Infos profile` et un reset du rappel profil.
|
|
116
118
|
|
|
117
119
|
### Redirections automatiques (optionnel)
|
|
118
120
|
|
|
@@ -175,7 +177,7 @@ const handleLogout = async () => {
|
|
|
175
177
|
|
|
176
178
|
1. **Révoque le token SaaS** — `POST /api/native/logout` (supprime le Sanctum token)
|
|
177
179
|
2. **Révoque la session IAM** — `POST /api/iam/disconnect` (avec `sanctum_token` + `app_access_token_ref`)
|
|
178
|
-
3. **Nettoie le localStorage** — supprime les
|
|
180
|
+
3. **Nettoie le localStorage** — supprime les clés de session du package
|
|
179
181
|
|
|
180
182
|
Les appels réseau sont en `Promise.allSettled` (best-effort) : même si le serveur est injoignable, le localStorage est **toujours** nettoyé.
|
|
181
183
|
|
|
@@ -189,6 +191,9 @@ Les appels réseau sont en `Promise.allSettled` (best-effort) : même si le serv
|
|
|
189
191
|
| `account_type` | Type de compte (`user` ou `client`) |
|
|
190
192
|
| `alias_reference` | Référence de l'alias de connexion |
|
|
191
193
|
| `app_access_token_ref` | Référence de l'`AppAccessToken` IAM (pour revocation optimisée) |
|
|
194
|
+
| `refresh_token` | Refresh token SaaS (si activé) |
|
|
195
|
+
| `token_expires_at` | Expiration du token Sanctum (si fournie) |
|
|
196
|
+
| `refresh_expires_at` | Expiration du refresh token (si fournie) |
|
|
192
197
|
|
|
193
198
|
### ⛔ `clearAuthToken()` est déprécié
|
|
194
199
|
|
|
@@ -235,7 +240,7 @@ Backend SaaS:
|
|
|
235
240
|
configPrefix="iam"
|
|
236
241
|
accountType="user"
|
|
237
242
|
redirectAfterLogin="/dashboard"
|
|
238
|
-
redirectAfterLogout="/auth/sso"
|
|
243
|
+
redirectAfterLogout="/auth/native-sso"
|
|
239
244
|
/>
|
|
240
245
|
|
|
241
246
|
{/* Page login espace vendeur */}
|
|
@@ -329,7 +334,7 @@ return [
|
|
|
329
334
|
|
|
330
335
|
### Côté Backend SaaS — Controller multi-tenant
|
|
331
336
|
|
|
332
|
-
Tous les controllers Native (`config`, `exchange`, `check-token`, `logout`) doivent lire le header :
|
|
337
|
+
Tous les controllers Native (`config`, `exchange`, `check-token`, `refresh`, `logout`) doivent lire le header :
|
|
333
338
|
|
|
334
339
|
```php
|
|
335
340
|
class NativeConfigController extends Controller
|
|
@@ -374,7 +379,11 @@ class NativeConfigController extends Controller
|
|
|
374
379
|
}
|
|
375
380
|
```
|
|
376
381
|
|
|
377
|
-
> **⚠️ Important** : Appliquez la même logique `X-IAM-Config-Prefix` dans `exchange`, `check-token` et `logout`.
|
|
382
|
+
> **⚠️ Important** : Appliquez la même logique `X-IAM-Config-Prefix` dans `exchange`, `check-token`, `refresh` et `logout`.
|
|
383
|
+
|
|
384
|
+
Le package envoie aussi :
|
|
385
|
+
- `X-Device-Id` (stable par appareil / webview)
|
|
386
|
+
- `X-Session-UUID` (UUID stable par instance, pour différencier plusieurs sessions sur un même device)
|
|
378
387
|
|
|
379
388
|
---
|
|
380
389
|
|
|
@@ -618,7 +627,8 @@ import { useMobileRegistration } from '@ollaid/native-sso';
|
|
|
618
627
|
|
|
619
628
|
## Backend SaaS — Endpoints requis
|
|
620
629
|
|
|
621
|
-
Le backend SaaS (Laravel) doit exposer **
|
|
630
|
+
Le backend SaaS (Laravel) doit exposer **5 endpoints** : `config`, `exchange`, `check-token`, `refresh`, `logout`.
|
|
631
|
+
Voici les spécifications exactes :
|
|
622
632
|
|
|
623
633
|
### `GET /api/native/config`
|
|
624
634
|
|
|
@@ -638,7 +648,7 @@ Retourne un **token opaque chiffré** (`encrypted_credentials`) contenant les cr
|
|
|
638
648
|
```
|
|
639
649
|
|
|
640
650
|
**Principe :**
|
|
641
|
-
Le SaaS chiffre `
|
|
651
|
+
Le SaaS chiffre `secret_key + timestamp` en AES-256-CBC avec `OPENSSL_RAW_DATA`, en utilisant la `secret_key` elle-même comme clé de chiffrement (SHA-256 → 32 bytes).
|
|
642
652
|
Le frontend transporte le blob opaque + l'`app_key` en clair (non sensible) vers l'IAM.
|
|
643
653
|
L'IAM retrouve la `secret_key` via l'`app_key` dans sa table `applications`, puis déchiffre le blob.
|
|
644
654
|
|
|
@@ -648,24 +658,26 @@ L'IAM retrouve la `secret_key` via l'`app_key` dans sa table `applications`, pui
|
|
|
648
658
|
Route::get('/native/config', function () {
|
|
649
659
|
$appKey = config('services.iam.app_key');
|
|
650
660
|
$secretKey = config('services.iam.secret_key');
|
|
661
|
+
$iamApiUrl = config('services.iam.api_url');
|
|
651
662
|
|
|
652
663
|
// Clé AES = SHA-256 de la secret_key (32 bytes binaires)
|
|
653
|
-
$
|
|
664
|
+
$encryptionKey = hash('sha256', $secretKey, true);
|
|
654
665
|
$iv = random_bytes(16);
|
|
655
666
|
|
|
656
667
|
$payload = json_encode([
|
|
657
|
-
'app_key' => $appKey,
|
|
658
668
|
'secret_key' => $secretKey,
|
|
659
669
|
'ts' => time(),
|
|
660
670
|
]);
|
|
661
671
|
|
|
662
|
-
$encrypted = openssl_encrypt($payload, '
|
|
672
|
+
$encrypted = openssl_encrypt($payload, 'AES-256-CBC', $encryptionKey, OPENSSL_RAW_DATA, $iv);
|
|
663
673
|
|
|
664
674
|
return response()->json([
|
|
665
|
-
'success'
|
|
666
|
-
'app_key'
|
|
667
|
-
'encrypted_credentials' => base64_encode($iv . '::' . $encrypted),
|
|
668
|
-
'
|
|
675
|
+
'success' => true,
|
|
676
|
+
'app_key' => $appKey, // En clair (non sensible, sert d'identifiant)
|
|
677
|
+
'encrypted_credentials' => base64_encode($iv . '::' . base64_encode($encrypted)),
|
|
678
|
+
'iam_api_url' => $iamApiUrl,
|
|
679
|
+
'credentials_ttl' => 300,
|
|
680
|
+
'debug' => (bool) config('services.iam.debug'),
|
|
669
681
|
]);
|
|
670
682
|
});
|
|
671
683
|
```
|
|
@@ -680,12 +692,12 @@ if (!$app) {
|
|
|
680
692
|
}
|
|
681
693
|
|
|
682
694
|
// 2. Déchiffrer avec la secret_key de l'application
|
|
683
|
-
$
|
|
695
|
+
$encryptionKey = hash('sha256', $app->secret_key, true);
|
|
684
696
|
$decoded = base64_decode($request->encrypted_credentials);
|
|
685
|
-
[$iv, $
|
|
697
|
+
[$iv, $ciphertextB64] = explode('::', $decoded, 2);
|
|
686
698
|
|
|
687
699
|
$payload = json_decode(
|
|
688
|
-
openssl_decrypt($
|
|
700
|
+
openssl_decrypt(base64_decode($ciphertextB64), 'AES-256-CBC', $encryptionKey, OPENSSL_RAW_DATA, $iv),
|
|
689
701
|
true
|
|
690
702
|
);
|
|
691
703
|
|
|
@@ -694,7 +706,6 @@ if (!$payload || time() - $payload['ts'] > 300) {
|
|
|
694
706
|
return response()->json(['success' => false, 'message' => 'Credentials expirés ou invalides'], 401);
|
|
695
707
|
}
|
|
696
708
|
|
|
697
|
-
$appKey = $payload['app_key'];
|
|
698
709
|
$secretKey = $payload['secret_key'];
|
|
699
710
|
// ... valider et continuer le flux
|
|
700
711
|
```
|
|
@@ -721,8 +732,8 @@ $secretKey = $payload['secret_key'];
|
|
|
721
732
|
|
|
722
733
|
**Logique backend :**
|
|
723
734
|
1. Recevoir le `callback_token`
|
|
724
|
-
2. Appeler l'IAM `POST /
|
|
725
|
-
3. L'IAM retourne les `user_infos` (9 champs standardisés)
|
|
735
|
+
2. Appeler l'IAM `POST /iam/auth/decrypt` avec les credentials dans les headers (`X-IAM-App-Key`, `X-IAM-Secret-Key`) et le token dans le body
|
|
736
|
+
3. L'IAM retourne les `user_infos` (9 champs standardisés) + `app_access_token_ref`
|
|
726
737
|
4. Créer ou mettre à jour l'utilisateur local
|
|
727
738
|
5. Générer un token Sanctum
|
|
728
739
|
6. Retourner le token + user
|
|
@@ -753,16 +764,14 @@ $secretKey = $payload['secret_key'];
|
|
|
753
764
|
```php
|
|
754
765
|
// routes/api.php
|
|
755
766
|
Route::post('/native/exchange', function (Request $request) {
|
|
756
|
-
$
|
|
767
|
+
$iamApiUrl = config('services.iam.api_url');
|
|
757
768
|
|
|
758
|
-
//
|
|
759
|
-
$
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
'
|
|
764
|
-
'secret_key' => config('services.iam.secret_key'),
|
|
765
|
-
'callback_token' => $callbackToken,
|
|
769
|
+
// Appel IAM pour décrypter — credentials dans les HEADERS
|
|
770
|
+
$response = Http::withHeaders([
|
|
771
|
+
'X-IAM-App-Key' => config('services.iam.app_key'),
|
|
772
|
+
'X-IAM-Secret-Key' => config('services.iam.secret_key'),
|
|
773
|
+
])->post("{$iamApiUrl}/iam/auth/decrypt", [
|
|
774
|
+
'token' => $request->callback_token,
|
|
766
775
|
]);
|
|
767
776
|
|
|
768
777
|
if (!$response->successful() || !$response->json('success')) {
|
|
@@ -773,7 +782,9 @@ Route::post('/native/exchange', function (Request $request) {
|
|
|
773
782
|
], 401);
|
|
774
783
|
}
|
|
775
784
|
|
|
776
|
-
$
|
|
785
|
+
$data = $response->json();
|
|
786
|
+
$userInfos = $data['user_infos'];
|
|
787
|
+
$appAccessTokenRef = $data['app_access_token_ref'] ?? null;
|
|
777
788
|
|
|
778
789
|
// Créer ou mettre à jour l'utilisateur local
|
|
779
790
|
$user = User::updateOrCreate(
|
|
@@ -786,16 +797,13 @@ Route::post('/native/exchange', function (Request $request) {
|
|
|
786
797
|
'image' => $userInfos['image_url'] ?? null,
|
|
787
798
|
'alias_reference' => $userInfos['alias_reference'],
|
|
788
799
|
'user_infos' => json_encode($userInfos),
|
|
789
|
-
'password' => bcrypt(Str::random(32)),
|
|
800
|
+
'password' => bcrypt(Str::random(32)),
|
|
790
801
|
]
|
|
791
802
|
);
|
|
792
803
|
|
|
793
804
|
// Générer un token Sanctum
|
|
794
805
|
$token = $user->createToken('native-sso')->plainTextToken;
|
|
795
806
|
|
|
796
|
-
// Récupérer app_access_token_ref depuis la réponse IAM
|
|
797
|
-
$appAccessTokenRef = $response->json('user_infos.app_access_token_ref');
|
|
798
|
-
|
|
799
807
|
return response()->json([
|
|
800
808
|
'success' => true,
|
|
801
809
|
'token' => $token,
|
|
@@ -828,6 +836,7 @@ Vérifie la validité du token Sanctum et retourne les infos utilisateur fraîch
|
|
|
828
836
|
**Réponse succès (200) :**
|
|
829
837
|
```json
|
|
830
838
|
{
|
|
839
|
+
"success": true,
|
|
831
840
|
"status": "connected",
|
|
832
841
|
"user": {
|
|
833
842
|
"name": "John Doe",
|
|
@@ -851,6 +860,7 @@ Route::post('/native/check-token', function (Request $request) {
|
|
|
851
860
|
$user = $request->user();
|
|
852
861
|
|
|
853
862
|
return response()->json([
|
|
863
|
+
'success' => true,
|
|
854
864
|
'status' => 'connected',
|
|
855
865
|
'user' => [
|
|
856
866
|
'name' => $user->name,
|
|
@@ -866,7 +876,46 @@ Route::post('/native/check-token', function (Request $request) {
|
|
|
866
876
|
})->middleware('auth:sanctum');
|
|
867
877
|
```
|
|
868
878
|
|
|
869
|
-
> **
|
|
879
|
+
> **Règle de déconnexion :** Le package ne déconnecte l'utilisateur **que** sur un **HTTP 401 explicite**. Tout HTTP 200 (quelle que soit la structure du body) confirme la session. Erreur réseau, timeout, 5xx, offline → session conservée, jamais de déconnexion inutile.
|
|
880
|
+
|
|
881
|
+
---
|
|
882
|
+
|
|
883
|
+
### `POST /api/native/refresh` (OBLIGATOIRE pour la stabilité)
|
|
884
|
+
|
|
885
|
+
Renouvelle la session **sans déconnecter**. Le package l'utilise :
|
|
886
|
+
- en **proactif** (avant expiration, si `expires_at` est fourni)
|
|
887
|
+
- en **récupération** quand `check-token` retourne `401` (tente un refresh avant logout)
|
|
888
|
+
|
|
889
|
+
**Body :**
|
|
890
|
+
```json
|
|
891
|
+
{ "refresh_token": "rt_..." }
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
**Recommandation (stabilité maximale) :** ne pas changer le token Sanctum.
|
|
895
|
+
Le refresh doit **prolonger `expires_at`** du token existant (le client garde son token actuel).
|
|
896
|
+
|
|
897
|
+
**Réponse succès (200) :**
|
|
898
|
+
```json
|
|
899
|
+
{
|
|
900
|
+
"success": true,
|
|
901
|
+
"expires_at": "2026-06-13T12:00:00+00:00",
|
|
902
|
+
"refresh_token": "rt_new_...",
|
|
903
|
+
"refresh_expires_at": "2026-08-13T12:00:00+00:00",
|
|
904
|
+
"user": { "name": "John Doe" }
|
|
905
|
+
}
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
**Réponse refresh invalide (401) :**
|
|
909
|
+
```json
|
|
910
|
+
{
|
|
911
|
+
"success": false,
|
|
912
|
+
"error_type": "invalid_refresh",
|
|
913
|
+
"message": "Refresh token invalide ou expiré"
|
|
914
|
+
}
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
> Si le refresh est invalide (`401 invalid_refresh`) : c'est une révocation explicite, la déconnexion est autorisée.
|
|
918
|
+
> Si offline/timeout/5xx : ne jamais déconnecter.
|
|
870
919
|
|
|
871
920
|
---
|
|
872
921
|
|
|
@@ -902,7 +951,7 @@ Route::post('/native/logout', function (Request $request) {
|
|
|
902
951
|
|
|
903
952
|
## Controller Laravel Complet (copier-coller)
|
|
904
953
|
|
|
905
|
-
Voici un `NativeAuthController.php` complet regroupant les
|
|
954
|
+
Voici un `NativeAuthController.php` complet regroupant les 5 endpoints. Copiez-le dans `app/Http/Controllers/Api/NativeAuthController.php` :
|
|
906
955
|
|
|
907
956
|
```php
|
|
908
957
|
<?php
|
|
@@ -1124,6 +1173,7 @@ class NativeAuthController extends Controller
|
|
|
1124
1173
|
$user = $request->user();
|
|
1125
1174
|
|
|
1126
1175
|
return response()->json([
|
|
1176
|
+
'success' => true,
|
|
1127
1177
|
'status' => 'connected',
|
|
1128
1178
|
'message' => 'Utilisateur connecté',
|
|
1129
1179
|
'user' => [
|
|
@@ -1290,7 +1340,7 @@ Ajoutez ces variables dans le fichier `.env` du backend SaaS :
|
|
|
1290
1340
|
# Credentials IAM (récupérés depuis le dashboard iam.ollaid.com)
|
|
1291
1341
|
IAM_APP_KEY=oiam_ak_xxxxxxxxxxxxxxxxxxxxxxxx
|
|
1292
1342
|
IAM_SECRET_KEY=oiam_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
1293
|
-
|
|
1343
|
+
IAM_API_URL=https://identityam.ollaid.com/api
|
|
1294
1344
|
|
|
1295
1345
|
# Mode debug — contrôle le DebugPanel et les logs côté frontend
|
|
1296
1346
|
# true : active le DebugPanel + logs console + détails d'erreur dans les réponses JSON
|
|
@@ -1307,7 +1357,7 @@ Et dans `config/services.php` :
|
|
|
1307
1357
|
'iam' => [
|
|
1308
1358
|
'app_key' => env('IAM_APP_KEY'),
|
|
1309
1359
|
'secret_key' => env('IAM_SECRET_KEY'),
|
|
1310
|
-
'
|
|
1360
|
+
'api_url' => env('IAM_API_URL', 'https://identityam.ollaid.com/api'),
|
|
1311
1361
|
'debug' => env('IAM_DEBUG', false), // Active le debug côté frontend (false si absent)
|
|
1312
1362
|
],
|
|
1313
1363
|
```
|
|
@@ -1342,6 +1392,14 @@ public function up(): void
|
|
|
1342
1392
|
|
|
1343
1393
|
> **Note :** Le champ `reference` est l'identifiant unique IAM de l'utilisateur. Le champ `alias_reference` identifie l'utilisateur dans le contexte d'une application spécifique.
|
|
1344
1394
|
|
|
1395
|
+
### Colonnes recommandées sur `personal_access_tokens` (Sanctum)
|
|
1396
|
+
|
|
1397
|
+
Pour la révocation synchronisée et la stabilité de session :
|
|
1398
|
+
- `app_access_token_ref` (révocation IAM ciblée)
|
|
1399
|
+
- `refresh_token_hash` + `refresh_expires_at` (refresh token)
|
|
1400
|
+
|
|
1401
|
+
Ces migrations sont détaillées dans `BACKEND_INTEGRATION.md` du package.
|
|
1402
|
+
|
|
1345
1403
|
---
|
|
1346
1404
|
|
|
1347
1405
|
## Flux d'authentification
|
|
@@ -1386,7 +1444,7 @@ public function up(): void
|
|
|
1386
1444
|
4. **Validate** — Le frontend envoie le mot de passe/OTP, l'IAM retourne un `callback_token`
|
|
1387
1445
|
5. **Exchange** — Le frontend envoie le `callback_token` au backend SaaS, qui le décrypte via l'IAM et crée une session Sanctum
|
|
1388
1446
|
|
|
1389
|
-
> **Important :** Le package gère les étapes 1-5 automatiquement. Le backend SaaS doit implémenter **
|
|
1447
|
+
> **Important :** Le package gère les étapes 1-5 automatiquement. Le backend SaaS doit implémenter **5 endpoints** (`config`, `exchange`, `check-token`, `refresh`, `logout`).
|
|
1390
1448
|
|
|
1391
1449
|
---
|
|
1392
1450
|
|
|
@@ -1896,7 +1954,7 @@ Toutes les APIs IAM retournent le même objet `user_infos` avec exactement **9 c
|
|
|
1896
1954
|
|
|
1897
1955
|
## Session & localStorage
|
|
1898
1956
|
|
|
1899
|
-
Le package utilise **
|
|
1957
|
+
Le package utilise **9 clés** dans `localStorage` pour persister la session et le suivi du profil :
|
|
1900
1958
|
|
|
1901
1959
|
| Clé | Contenu | Source | Valeurs possibles |
|
|
1902
1960
|
|-----|---------|--------|-------------------|
|
|
@@ -1906,8 +1964,12 @@ Le package utilise **6 clés** dans `localStorage` pour persister la session :
|
|
|
1906
1964
|
| `account_type` | Type de compte | Déterminé lors du `exchange` selon le mode d'inscription | `"user"` (défaut) ou `"client"` (inscription phone-only) |
|
|
1907
1965
|
| `alias_reference` | Référence alias utilisée lors de la connexion | Réponse de `/api/native/exchange` (champ `user.alias_reference`) | `"ALI-XXXXXXXX"` |
|
|
1908
1966
|
| `app_access_token_ref` | Référence de l'`AppAccessToken` IAM | Réponse de `/api/native/exchange` (champ `app_access_token_ref`) | `"42"` ou `"aat_ref_abc123"` |
|
|
1967
|
+
| `sso_image_last_status` | Statut du dernier onboarding profil | Mis à jour par `OnboardingModal` | `"true"` ou `"false"` |
|
|
1968
|
+
| `sso_image_last_check` | Timestamp du dernier contrôle profil | Mis à jour par `OnboardingModal` / sync profil | Millisecondes Unix |
|
|
1969
|
+
| `sso_image_recheck_at` | Timestamp de rappel après snooze | Mis à jour quand l'utilisateur passe l'onboarding | Millisecondes Unix |
|
|
1909
1970
|
|
|
1910
1971
|
> **Note :** `account_type` et `alias_reference` sont stockés **séparément** de l'objet `user` en plus d'être inclus dans le JSON de la clé `user`.
|
|
1972
|
+
> **Note :** `sso_image_last_status`, `sso_image_last_check` et `sso_image_recheck_at` vivent dans le `localStorage` du SaaS et servent au rappel d'onboarding du profil.
|
|
1911
1973
|
|
|
1912
1974
|
#### Champs enrichis dans l'objet `user`
|
|
1913
1975
|
|
|
@@ -1917,7 +1979,8 @@ L'objet `user` stocké en localStorage contient deux champs ajoutés automatique
|
|
|
1917
1979
|
|
|
1918
1980
|
### Nettoyage
|
|
1919
1981
|
|
|
1920
|
-
Lors du `logout()`, les 6 clés sont supprimées via `clearAuthToken()`.
|
|
1982
|
+
Lors du `logout()`, les 6 clés de session sont supprimées via `clearAuthToken()`.
|
|
1983
|
+
Les clés de rappel profil (`sso_image_*`) ne sont pas effacées, afin de conserver le comportement de relance après snooze.
|
|
1921
1984
|
|
|
1922
1985
|
### Accès programmatique
|
|
1923
1986
|
|
|
@@ -2054,7 +2117,7 @@ Le [health check](#usetokenhealthcheck) (toutes les 2 min) détecte automatiquem
|
|
|
2054
2117
|
|
|
2055
2118
|
---
|
|
2056
2119
|
|
|
2057
|
-
Modal post-connexion qui invite l'utilisateur à compléter les informations manquantes de son profil.
|
|
2120
|
+
Modal post-connexion qui invite l'utilisateur à compléter les informations manquantes de son profil, ou à éditer ses informations complètes depuis un SaaS.
|
|
2058
2121
|
|
|
2059
2122
|
### Props
|
|
2060
2123
|
|
|
@@ -2063,35 +2126,102 @@ Modal post-connexion qui invite l'utilisateur à compléter les informations man
|
|
|
2063
2126
|
| `open` | `boolean` | ✅ | Contrôle l'ouverture de la modal |
|
|
2064
2127
|
| `onOpenChange` | `(open: boolean) => void` | ✅ | Callback changement d'état |
|
|
2065
2128
|
| `user` | `NativeUser` | ✅ | Objet utilisateur courant (pour détecter les champs manquants) |
|
|
2129
|
+
| `variant` | `'missing' \| 'edit'` | ❌ | `missing` affiche uniquement les champs absents, `edit` affiche les champs de base en mode édition complet |
|
|
2066
2130
|
| `onComplete` | `(data) => void` | ✅ | Callback avec les données saisies |
|
|
2067
2131
|
| `onSkip` | `() => void` | ✅ | Callback si l'utilisateur passe l'étape |
|
|
2068
2132
|
|
|
2069
2133
|
### Champs affichés
|
|
2070
2134
|
|
|
2071
|
-
La modal
|
|
2072
|
-
|
|
2073
|
-
- **
|
|
2074
|
-
- **
|
|
2135
|
+
La modal gère **deux modes** :
|
|
2136
|
+
|
|
2137
|
+
- **Mode `missing`** : affiche uniquement les champs manquants
|
|
2138
|
+
- **Nom complet** — si `user.name` est vide
|
|
2139
|
+
- **Photo de profil** — si `user.image_url` est vide (max 2 Mo, JPG/PNG)
|
|
2140
|
+
- **Numéro de téléphone** — si `user.phone` est vide
|
|
2141
|
+
- **Adresse email** — si `user.email` est vide
|
|
2142
|
+
- **Mode `edit`** : affiche les champs de base éditables même si le profil est déjà complet
|
|
2143
|
+
- Nom complet
|
|
2144
|
+
- Photo de profil
|
|
2145
|
+
- Numéro de téléphone en lecture seule avec bouton `Changer téléphone`
|
|
2146
|
+
- Adresse email en lecture seule avec bouton `Changer email`
|
|
2075
2147
|
|
|
2076
2148
|
### Callback `onComplete`
|
|
2077
2149
|
|
|
2078
2150
|
```ts
|
|
2079
2151
|
onComplete: (data: {
|
|
2152
|
+
name?: string; // Nom complet (si fourni ou modifié)
|
|
2080
2153
|
image_url?: string; // Base64 de la photo (si ajoutée)
|
|
2081
2154
|
ccphone?: string; // Indicatif (si téléphone ajouté)
|
|
2082
2155
|
phone?: string; // Numéro (si téléphone ajouté)
|
|
2083
|
-
email?: string; // Email (si renseigné
|
|
2156
|
+
email?: string; // Email (si renseigné)
|
|
2084
2157
|
}) => void;
|
|
2085
2158
|
```
|
|
2086
2159
|
|
|
2087
2160
|
### Condition de soumission
|
|
2088
2161
|
|
|
2089
2162
|
L'utilisateur **doit** :
|
|
2090
|
-
1.
|
|
2091
|
-
2. Fournir une photo
|
|
2092
|
-
3. Fournir un téléphone valide
|
|
2163
|
+
1. Fournir un nom si le nom est affiché
|
|
2164
|
+
2. Fournir une photo si elle est affichée
|
|
2165
|
+
3. Fournir un téléphone valide si le champ est affiché
|
|
2166
|
+
4. Fournir un email valide si le champ est affiché
|
|
2167
|
+
|
|
2168
|
+
Après connexion, la page autonome attend 5 minutes avant d'ouvrir cette modal si le profil est incomplet.
|
|
2169
|
+
Si l'utilisateur la ferme sans compléter, le package enregistre un snooze de 8 heures (`sso_image_last_status=false`, `sso_image_last_check=now`, `sso_image_recheck_at=now+8h`).
|
|
2170
|
+
Un sync profil best-effort relit ensuite l'état utilisateur toutes les 30 minutes et remet `sso_image_last_status=true` dès que le profil est complet.
|
|
2171
|
+
|
|
2172
|
+
### Mode automatique
|
|
2173
|
+
|
|
2174
|
+
Le mode automatique est piloté par `NativeSSOPage` :
|
|
2175
|
+
- la modal s'ouvre après le délai de 5 minutes si des infos sont manquantes,
|
|
2176
|
+
- elle reste sur un écran de chargement tant que l'hydratation IAM n'a pas renvoyé le profil,
|
|
2177
|
+
- elle affiche ensuite uniquement les champs manquants,
|
|
2178
|
+
- si l'utilisateur ferme la modal, le rappel est repoussé de 8 heures.
|
|
2179
|
+
|
|
2180
|
+
### Mode manuel depuis un SaaS
|
|
2181
|
+
|
|
2182
|
+
Quand vous ouvrez la modal depuis votre SaaS via un bouton "Infos profil" ou "Modifier mon profil", utilisez `variant="edit"`.
|
|
2093
2183
|
|
|
2094
|
-
|
|
2184
|
+
Exemple :
|
|
2185
|
+
|
|
2186
|
+
```tsx
|
|
2187
|
+
import { useState } from 'react';
|
|
2188
|
+
import { OnboardingModal } from '@ollaid/native-sso';
|
|
2189
|
+
|
|
2190
|
+
export function ProfileButton({ user }: { user: NativeUser }) {
|
|
2191
|
+
const [open, setOpen] = useState(false);
|
|
2192
|
+
|
|
2193
|
+
return (
|
|
2194
|
+
<>
|
|
2195
|
+
<button type="button" onClick={() => setOpen(true)}>
|
|
2196
|
+
Infos profil
|
|
2197
|
+
</button>
|
|
2198
|
+
|
|
2199
|
+
<OnboardingModal
|
|
2200
|
+
open={open}
|
|
2201
|
+
onOpenChange={setOpen}
|
|
2202
|
+
onDismiss={() => setOpen(false)}
|
|
2203
|
+
user={user}
|
|
2204
|
+
variant="edit"
|
|
2205
|
+
profileHydrating={false}
|
|
2206
|
+
onComplete={(data) => {
|
|
2207
|
+
// Mettre à jour votre cache local / localStorage avec data.user_infos
|
|
2208
|
+
setOpen(false);
|
|
2209
|
+
}}
|
|
2210
|
+
onSkip={() => setOpen(false)}
|
|
2211
|
+
/>
|
|
2212
|
+
</>
|
|
2213
|
+
);
|
|
2214
|
+
}
|
|
2215
|
+
```
|
|
2216
|
+
|
|
2217
|
+
Dans ce mode, le changement de téléphone/email ouvre un sous-flux OTP dans la même modal:
|
|
2218
|
+
1. saisie du nouveau téléphone/email,
|
|
2219
|
+
2. envoi du premier OTP sur le contact actuel,
|
|
2220
|
+
3. vérification du premier OTP,
|
|
2221
|
+
4. envoi d'un second OTP sur le nouveau contact,
|
|
2222
|
+
5. confirmation finale et mise à jour du profil.
|
|
2223
|
+
|
|
2224
|
+
Les écrans de changement restent en `static backdrop` côté package: un clic hors modal ne ferme pas le flux.
|
|
2095
2225
|
|
|
2096
2226
|
### Exemple
|
|
2097
2227
|
|
|
@@ -2102,6 +2232,7 @@ import { OnboardingModal } from '@ollaid/native-sso';
|
|
|
2102
2232
|
open={showOnboarding}
|
|
2103
2233
|
onOpenChange={setShowOnboarding}
|
|
2104
2234
|
user={currentUser}
|
|
2235
|
+
variant="edit"
|
|
2105
2236
|
onComplete={async (data) => {
|
|
2106
2237
|
// Envoyer les données au backend pour mise à jour
|
|
2107
2238
|
await api.updateProfile(data);
|
|
@@ -2250,6 +2381,9 @@ if (config('services.iam.debug')) {
|
|
|
2250
2381
|
- `getAuthToken` — Récupérer le token depuis localStorage
|
|
2251
2382
|
- `getAuthUser` — Récupérer l'utilisateur depuis localStorage
|
|
2252
2383
|
- `getAccountType` — Récupérer le type de compte depuis localStorage
|
|
2384
|
+
- `getDeviceId` — Récupérer/générer le `X-Device-Id` (persisté)
|
|
2385
|
+
- `getSessionUuid` — Récupérer/générer le `X-Session-UUID` (persisté)
|
|
2386
|
+
- `STORAGE_KEYS` — Constantes des clés localStorage utilisées par le package
|
|
2253
2387
|
|
|
2254
2388
|
### Types
|
|
2255
2389
|
- `UserInfos`, `NativeAuthState`, `NativeAuthStatus`, `NativeCredentials`, etc.
|
|
@@ -2354,3 +2488,16 @@ npm publish --access public # 3. Publier sur npm
|
|
|
2354
2488
|
## Licence
|
|
2355
2489
|
|
|
2356
2490
|
Propriétaire — Ollaid © 2026
|
|
2491
|
+
|
|
2492
|
+
---
|
|
2493
|
+
|
|
2494
|
+
## Webhooks & Health Check (Backend)
|
|
2495
|
+
|
|
2496
|
+
Pour garantir la fiabilité et la synchronisation en temps réel (ex: révocation instantanée d'une session bannie), votre backend doit implémenter deux briques supplémentaires.
|
|
2497
|
+
|
|
2498
|
+
Consultez le guide détaillé : [**WEBHOOKS_HEALTH.md**](./WEBHOOKS_HEALTH.md)
|
|
2499
|
+
|
|
2500
|
+
| Brique | Rôle |
|
|
2501
|
+
|--------|------|
|
|
2502
|
+
| **Health Check** | Permet à l'IAM de vérifier que votre SaaS est "Healthy" et bien configuré. |
|
|
2503
|
+
| **Webhooks** | Permet à l'IAM de notifier votre SaaS d'événements critiques (suspension, révocation). |
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AvatarCropModal — recadrage carré simple et déterministe
|
|
3
|
+
*
|
|
4
|
+
* Version réécrite from scratch pour éviter les états qui bloquent le bouton Valider.
|
|
5
|
+
*
|
|
6
|
+
* @version 2.5.0
|
|
7
|
+
*/
|
|
8
|
+
export interface AvatarCropModalProps {
|
|
9
|
+
open: boolean;
|
|
10
|
+
imageSrc: string | null;
|
|
11
|
+
onOpenChange: (open: boolean) => void;
|
|
12
|
+
onCancel: () => void;
|
|
13
|
+
onConfirm: (blob: Blob) => Promise<void> | void;
|
|
14
|
+
}
|
|
15
|
+
export declare function AvatarCropModal({ open, imageSrc, onOpenChange, onCancel, onConfirm }: AvatarCropModalProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export default AvatarCropModal;
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
* DebugPanel — Panneau de debug flottant pour @ollaid/native-sso
|
|
3
3
|
* Affiche l'historique des appels API en temps réel (style terminal)
|
|
4
4
|
* N'apparaît que quand debug=true
|
|
5
|
-
* @version
|
|
5
|
+
* @version 2.5.0
|
|
6
6
|
*/
|
|
7
|
+
export type DebugOnboardingPreset = 'current' | 'photo' | 'phone' | 'email' | 'all';
|
|
7
8
|
interface DebugPanelProps {
|
|
8
9
|
saasApiUrl: string;
|
|
9
10
|
iamApiUrl: string;
|
|
11
|
+
onOpenLogin?: () => void;
|
|
12
|
+
onOpenSignup?: () => void;
|
|
13
|
+
onOpenOnboarding?: (preset: DebugOnboardingPreset) => void;
|
|
14
|
+
onResetProfilePrompt?: () => void;
|
|
10
15
|
}
|
|
11
|
-
export declare function DebugPanel({ saasApiUrl, iamApiUrl }: DebugPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export declare function DebugPanel({ saasApiUrl, iamApiUrl, onOpenLogin, onOpenSignup, onOpenOnboarding, onResetProfilePrompt }: DebugPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
12
17
|
export default DebugPanel;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Login Modal for @ollaid/native-sso
|
|
3
|
-
* Complete login flow aligned with
|
|
3
|
+
* Complete login flow aligned with Native SSO design
|
|
4
4
|
*
|
|
5
|
-
* @version
|
|
5
|
+
* @version 2.5.0
|
|
6
6
|
*/
|
|
7
7
|
import type { UserInfos } from '../types/native';
|
|
8
8
|
export interface LoginModalProps {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* NativeSSOPage — Page autonome complète pour @ollaid/native-sso
|
|
3
|
-
* Design aligné sur
|
|
3
|
+
* Design aligné sur le parcours Native SSO (fond primary, card blanche, ShieldCheck branding)
|
|
4
4
|
*
|
|
5
|
-
* @version 2.
|
|
5
|
+
* @version 2.5.0
|
|
6
6
|
*/
|
|
7
7
|
import type { UserInfos } from '../types/native';
|
|
8
8
|
export interface NativeSSOPageProps {
|
|
@@ -11,9 +11,15 @@ export interface NativeSSOPageProps {
|
|
|
11
11
|
onLoginSuccess?: (token: string, user: UserInfos) => void;
|
|
12
12
|
onLogout?: () => void;
|
|
13
13
|
onOnboardingComplete?: (data: {
|
|
14
|
+
name?: string;
|
|
14
15
|
image_url?: string;
|
|
15
16
|
ccphone?: string;
|
|
16
17
|
phone?: string;
|
|
18
|
+
email?: string;
|
|
19
|
+
address?: string;
|
|
20
|
+
town?: string;
|
|
21
|
+
country?: string;
|
|
22
|
+
user_infos?: UserInfos;
|
|
17
23
|
}) => void;
|
|
18
24
|
accountType?: 'user' | 'client';
|
|
19
25
|
configPrefix?: string;
|
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OnboardingModal —
|
|
3
|
-
*
|
|
2
|
+
* OnboardingModal — Modal de profil unifiée
|
|
3
|
+
* Mode `missing` : champs absents uniquement
|
|
4
|
+
* Mode `edit` : édition complète du profil depuis le SaaS
|
|
4
5
|
*
|
|
5
|
-
* @version
|
|
6
|
+
* @version 2.5.0
|
|
6
7
|
*/
|
|
7
|
-
import type { NativeUser } from '../types/native';
|
|
8
|
+
import type { NativeUser, UserInfos } from '../types/native';
|
|
8
9
|
export interface OnboardingModalProps {
|
|
9
10
|
open: boolean;
|
|
10
11
|
onOpenChange: (open: boolean) => void;
|
|
12
|
+
onDismiss: () => void;
|
|
11
13
|
user: NativeUser;
|
|
12
|
-
|
|
14
|
+
variant?: 'missing' | 'edit';
|
|
15
|
+
profileHydrating?: boolean;
|
|
13
16
|
onComplete: (data: {
|
|
17
|
+
name?: string;
|
|
14
18
|
image_url?: string;
|
|
15
19
|
ccphone?: string;
|
|
16
20
|
phone?: string;
|
|
17
21
|
email?: string;
|
|
22
|
+
address?: string;
|
|
23
|
+
town?: string;
|
|
24
|
+
country?: string;
|
|
25
|
+
user_infos?: UserInfos;
|
|
18
26
|
}) => void;
|
|
19
|
-
/** Called when user skips onboarding */
|
|
20
27
|
onSkip: () => void;
|
|
21
28
|
}
|
|
22
|
-
export declare function OnboardingModal({ open, onOpenChange, user, onComplete, onSkip }: OnboardingModalProps): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
export declare function OnboardingModal({ open, onOpenChange, onDismiss, user, variant, profileHydrating, onComplete, onSkip }: OnboardingModalProps): import("react/jsx-runtime").JSX.Element;
|
|
23
30
|
export default OnboardingModal;
|