@ollaid/native-sso 1.0.7 → 1.0.8

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
@@ -11,19 +11,22 @@ Package NPM Frontend-First pour l'authentification Native SSO Ollaid.
11
11
  2. [Intégration rapide (3 étapes)](#intégration-rapide-3-étapes)
12
12
  3. [Props de NativeSSOPage](#props-de-nativessopage)
13
13
  4. [Usage avancé (composants individuels)](#usage-avancé-composants-individuels)
14
- 5. [Backend SaaS Endpoints requis](#backend-saas--endpoints-requis)
15
- 6. [APIs IAM Account (Server-to-Server)](#apis-iam-account-server-to-server)
16
- 7. [Avatar par application](#avatar-par-application)
17
- 8. [Réponses d'erreur](#réponses-derreur)
18
- 9. [Configuration .env Laravel](#configuration-env-laravel)
19
- 10. [Migration Laravel](#migration-laravel)
20
- 11. [Flux d'authentification](#flux-dauthentification)
21
- 12. [Session & localStorage](#session--localstorage)
22
- 13. [OnboardingModal](#onboardingmodal)
23
- 14. [useTokenHealthCheck](#usetokenhealthcheck)
24
- 15. [Sécurité](#sécurité)
25
- 16. [Exports](#exports)
26
- 17. [Publication & Installation npm](#publication--installation-npm)
14
+ 5. [Props des composants individuels](#props-des-composants-individuels)
15
+ 6. [Gestion des conflits d'inscription](#gestion-des-conflits-dinscription)
16
+ 7. [Hook useMobileRegistration](#hook-usemobileregistration)
17
+ 8. [Backend SaaS — Endpoints requis](#backend-saas--endpoints-requis)
18
+ 9. [APIs IAM Account (Server-to-Server)](#apis-iam-account-server-to-server)
19
+ 10. [Avatar par application](#avatar-par-application)
20
+ 11. [Réponses d'erreur](#réponses-derreur)
21
+ 12. [Configuration .env Laravel](#configuration-env-laravel)
22
+ 13. [Migration Laravel](#migration-laravel)
23
+ 14. [Flux d'authentification](#flux-dauthentification)
24
+ 15. [Session & localStorage](#session--localstorage)
25
+ 16. [OnboardingModal](#onboardingmodal)
26
+ 17. [useTokenHealthCheck](#usetokenhealthcheck)
27
+ 18. [Sécurité](#sécurité)
28
+ 19. [Exports](#exports)
29
+ 20. [Publication & Installation npm](#publication--installation-npm)
27
30
 
28
31
  ---
29
32
 
@@ -414,6 +417,7 @@ function MyCustomAuth() {
414
417
  onLoginSuccess={(token, user) => console.log('OK', user)}
415
418
  saasApiUrl="https://mon-saas.com/api"
416
419
  iamApiUrl="https://identityam.ollaid.com/api"
420
+ defaultAccountType="user"
417
421
  />
418
422
  </>
419
423
  );
@@ -422,6 +426,170 @@ function MyCustomAuth() {
422
426
 
423
427
  ---
424
428
 
429
+ ## Props des composants individuels
430
+
431
+ ### `SignupModal`
432
+
433
+ | Prop | Type | Requis | Description |
434
+ |------|------|--------|-------------|
435
+ | `open` | `boolean` | ✅ | Contrôle l'ouverture du modal |
436
+ | `onOpenChange` | `(open: boolean) => void` | ✅ | Callback de changement d'état |
437
+ | `onSwitchToLogin` | `() => void` | ✅ | Callback pour basculer vers le login |
438
+ | `onSignupSuccess` | `(token: string, user: UserInfos) => void` | ✅ | Callback après inscription réussie |
439
+ | `saasApiUrl` | `string` | ✅ | URL du backend SaaS |
440
+ | `iamApiUrl` | `string` | ✅ | URL du backend IAM |
441
+ | `defaultAccountType` | `'user' \| 'client'` | ❌ | Hérité de `NativeSSOPage.accountType`. Type de compte à persister dans localStorage. Si non défini, la valeur par défaut est `'user'`. |
442
+ | `onSwitchToLoginWithPhone` | `(phone: string) => void` | ❌ | Callback lors d'un conflit phone +221 — permet de basculer vers `LoginModal` avec le numéro pré-rempli |
443
+
444
+ ### `LoginModal`
445
+
446
+ | Prop | Type | Requis | Description |
447
+ |------|------|--------|-------------|
448
+ | `open` | `boolean` | ✅ | Contrôle l'ouverture du modal |
449
+ | `onOpenChange` | `(open: boolean) => void` | ✅ | Callback de changement d'état |
450
+ | `onSwitchToSignup` | `() => void` | ✅ | Callback pour basculer vers l'inscription |
451
+ | `onLoginSuccess` | `(token: string, user: UserInfos) => void` | ❌ | Callback après connexion réussie |
452
+ | `saasApiUrl` | `string` | ✅ | URL du backend SaaS |
453
+ | `iamApiUrl` | `string` | ✅ | URL du backend IAM |
454
+ | `loading` | `boolean` | ❌ | État de chargement externe |
455
+ | `showSwitchToSignup` | `boolean` | ❌ | Afficher le lien "Pas de compte ? S'inscrire" (défaut: `true`) |
456
+ | `defaultAccountType` | `'user' \| 'client'` | ❌ | Hérité de `NativeSSOPage.accountType`. Type de compte à persister dans localStorage. Si non défini, la valeur par défaut est `'user'`. |
457
+ | `initialPhone` | `string` | ❌ | Pré-remplit le numéro de téléphone et va directement à l'étape `phone-input`. Utilisé par le hand-off depuis `SignupModal` lors d'un conflit. |
458
+
459
+ ---
460
+
461
+ ## Gestion des conflits d'inscription
462
+
463
+ Lorsqu'un utilisateur tente de s'inscrire avec un email ou un téléphone déjà associé à un compte existant, le backend retourne un **409 Conflict**. Le `SignupModal` gère automatiquement ce cas avec une vue dédiée (`ConflictView`).
464
+
465
+ ### Comportement automatique
466
+
467
+ 1. L'utilisateur remplit le formulaire d'inscription et soumet
468
+ 2. Le backend détecte un conflit (`email_exists` ou `phone_exists`) et retourne un objet `conflict`
469
+ 3. Le `SignupModal` affiche la `ConflictView` avec les options disponibles
470
+
471
+ ### Affichage intelligent des identifiants
472
+
473
+ - **L'identifiant saisi par l'utilisateur** (email ou téléphone) est affiché **en clair** pour confirmer sa saisie
474
+ - **L'identifiant lié au compte existant** (ex: l'email masqué associé à un numéro déjà enregistré) reste **masqué** pour la confidentialité (ex: `j***@gmail.com`)
475
+
476
+ ### Options proposées
477
+
478
+ Selon le type de conflit et les capacités du compte existant, la vue propose :
479
+
480
+ | Option | Condition | Action |
481
+ |--------|-----------|--------|
482
+ | Se connecter | `conflict.options.can_login === true` | Bascule vers `LoginModal` via `onSwitchToLogin` |
483
+ | Se connecter par téléphone | Conflit phone + numéro +221 | Bascule vers `LoginModal` avec le numéro pré-rempli via `onSwitchToLoginWithPhone(phone)` |
484
+ | Récupérer par email | `conflict.options.can_recover_by_email === true` | Ouvre le `PasswordRecoveryModal` |
485
+ | Récupérer par SMS | `conflict.options.can_recover_by_sms === true` | Ouvre le `PasswordRecoveryModal` |
486
+ | Modifier les informations | Toujours disponible | Retour au formulaire pour changer l'identifiant |
487
+
488
+ ### Exemple d'intégration avec hand-off téléphone
489
+
490
+ ```tsx
491
+ function AuthPage() {
492
+ const [showSignup, setShowSignup] = useState(false);
493
+ const [showLogin, setShowLogin] = useState(false);
494
+ const [loginPhone, setLoginPhone] = useState<string | undefined>();
495
+
496
+ return (
497
+ <>
498
+ <SignupModal
499
+ open={showSignup}
500
+ onOpenChange={setShowSignup}
501
+ onSwitchToLogin={() => { setShowSignup(false); setShowLogin(true); }}
502
+ onSwitchToLoginWithPhone={(phone) => {
503
+ setLoginPhone(phone);
504
+ setShowSignup(false);
505
+ setShowLogin(true);
506
+ }}
507
+ onSignupSuccess={(token, user) => navigate('/dashboard')}
508
+ saasApiUrl="https://mon-saas.com/api"
509
+ iamApiUrl="https://identityam.ollaid.com/api"
510
+ />
511
+
512
+ <LoginModal
513
+ open={showLogin}
514
+ onOpenChange={setShowLogin}
515
+ onSwitchToSignup={() => { setShowLogin(false); setShowSignup(true); }}
516
+ initialPhone={loginPhone}
517
+ saasApiUrl="https://mon-saas.com/api"
518
+ iamApiUrl="https://identityam.ollaid.com/api"
519
+ />
520
+ </>
521
+ );
522
+ }
523
+ ```
524
+
525
+ ### Type `RegistrationConflict`
526
+
527
+ L'objet retourné par le backend lors d'un conflit :
528
+
529
+ ```typescript
530
+ interface RegistrationConflict {
531
+ type: 'email' | 'phone';
532
+ masked_identifier: string;
533
+ options: {
534
+ can_login: boolean;
535
+ can_recover_by_email: boolean;
536
+ can_recover_by_sms: boolean;
537
+ masked_email?: string;
538
+ masked_phone?: string;
539
+ };
540
+ }
541
+ ```
542
+
543
+ ---
544
+
545
+ ## Hook `useMobileRegistration`
546
+
547
+ Hook React pour gérer le flow d'inscription mobile en 3 étapes : `init` → `verify-otp` → `complete`.
548
+
549
+ ### Import
550
+
551
+ ```tsx
552
+ import { useMobileRegistration } from '@ollaid/native-sso';
553
+ ```
554
+
555
+ ### Propriétés retournées
556
+
557
+ #### État
558
+
559
+ | Propriété | Type | Description |
560
+ |-----------|------|-------------|
561
+ | `processToken` | `string \| null` | Token de processus d'inscription en cours |
562
+ | `status` | `'idle' \| 'pending_otp' \| 'pending_password' \| 'pending_registration' \| 'completed'` | Étape actuelle du flow |
563
+ | `formData` | `Partial<MobileRegistrationFormData>` | Données du formulaire stockées |
564
+ | `loading` | `boolean` | Indique si une opération est en cours |
565
+ | `error` | `string \| null` | Message d'erreur (français, user-friendly) |
566
+ | `conflict` | `RegistrationConflict \| null` | Objet de conflit si l'identifiant existe déjà |
567
+ | `isCompleted` | `boolean` | `true` si l'inscription est terminée |
568
+ | `hasConflict` | `boolean` | `true` si un conflit est en cours |
569
+
570
+ #### Type de compte (phone-only)
571
+
572
+ | Propriété | Type | Description |
573
+ |-----------|------|-------------|
574
+ | `accountType` | `'email' \| 'phone-only'` | Type de compte sélectionné |
575
+ | `setAccountType` | `(type: AccountType) => void` | Change le type de compte |
576
+ | `isPhoneOnly` | `boolean` | `true` si `accountType === 'phone-only'` |
577
+
578
+ #### Méthodes
579
+
580
+ | Méthode | Signature | Description |
581
+ |---------|-----------|-------------|
582
+ | `updateFormData` | `(data: Partial<MobileRegistrationFormData>) => void` | Met à jour les données du formulaire |
583
+ | `initRegistration` | `(data) => Promise<{ success, otp_code_dev?, otp_method?, otp_sent_to? }>` | Initialise l'inscription (envoi OTP). Peut retourner un conflit. |
584
+ | `verifyOtp` | `(otpCode: string) => Promise<{ success, completed?, callback_token? }>` | Vérifie le code OTP |
585
+ | `completeRegistration` | `(password: string) => Promise<{ success, callback_token? }>` | Finalise avec mot de passe (type `email`) |
586
+ | `completePhoneOnlyRegistration` | `() => Promise<{ success, callback_token? }>` | Finalise sans mot de passe (type `phone-only`, Sénégal 🇸🇳) |
587
+ | `resendOtp` | `() => Promise<{ success, cooldown?, otp_code_dev? }>` | Renvoie le code OTP |
588
+ | `reset` | `() => void` | Réinitialise tout le hook |
589
+ | `clearError` | `() => void` | Efface l'erreur et le conflit |
590
+
591
+ ---
592
+
425
593
  ## Backend SaaS — Endpoints requis
426
594
 
427
595
  Le backend SaaS (Laravel) doit exposer **4 endpoints**. Voici les spécifications exactes :
@@ -1883,6 +2051,10 @@ if (config('services.iam.debug')) {
1883
2051
 
1884
2052
  ### Types
1885
2053
  - `UserInfos`, `NativeAuthState`, `NativeAuthStatus`, `NativeCredentials`, etc.
2054
+ - `AccountType` — `'email' | 'phone-only'`
2055
+ - `MobilePasswordState`, `MobilePasswordStatus` — Types pour la récupération de mot de passe
2056
+ - `MobileRegistrationFormData` — Données du formulaire d'inscription
2057
+ - `RegistrationConflict` — Objet de conflit d'inscription (type interne, voir [Gestion des conflits](#gestion-des-conflits-dinscription))
1886
2058
  - `LinkPhoneRequest`, `LinkPhoneResponse` — Types pour l'API link-phone
1887
2059
  - `LinkEmailRequest`, `LinkEmailResponse` — Types pour l'API link-email
1888
2060
  - `RefreshUserInfoSingleRequest`, `RefreshUserInfoSingleResponse` — Types pour refresh single
@@ -16,6 +16,8 @@ export interface LoginModalProps {
16
16
  showSwitchToSignup?: boolean;
17
17
  /** Type de compte par défaut à persister dans localStorage */
18
18
  defaultAccountType?: 'user' | 'client';
19
+ /** Pre-fill phone number and go directly to phone-input step */
20
+ initialPhone?: string;
19
21
  }
20
- export declare function LoginModal({ open, onOpenChange, onSwitchToSignup, onLoginSuccess, saasApiUrl, iamApiUrl, loading, showSwitchToSignup, defaultAccountType, }: LoginModalProps): import("react/jsx-runtime").JSX.Element;
22
+ export declare function LoginModal({ open, onOpenChange, onSwitchToSignup, onLoginSuccess, saasApiUrl, iamApiUrl, loading, showSwitchToSignup, defaultAccountType, initialPhone, }: LoginModalProps): import("react/jsx-runtime").JSX.Element;
21
23
  export default LoginModal;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Phone Input component for @ollaid/native-sso
3
- * Uses complete world countries list
3
+ * Custom select overlay: dropdown shows ISO code, selected display shows only flag + dialCode
4
4
  */
5
5
  interface PhoneInputProps {
6
6
  value: string;
@@ -14,6 +14,8 @@ export interface SignupModalProps {
14
14
  iamApiUrl: string;
15
15
  /** Type de compte par défaut à persister dans localStorage */
16
16
  defaultAccountType?: 'user' | 'client';
17
+ /** Called when conflict resolution wants to switch to login with a pre-filled phone */
18
+ onSwitchToLoginWithPhone?: (phone: string) => void;
17
19
  }
18
- export declare function SignupModal({ open, onOpenChange, onSwitchToLogin, onSignupSuccess, saasApiUrl, iamApiUrl, defaultAccountType }: SignupModalProps): import("react/jsx-runtime").JSX.Element;
20
+ export declare function SignupModal({ open, onOpenChange, onSwitchToLogin, onSignupSuccess, saasApiUrl, iamApiUrl, defaultAccountType, onSwitchToLoginWithPhone }: SignupModalProps): import("react/jsx-runtime").JSX.Element;
19
21
  export default SignupModal;
@@ -34,6 +34,16 @@ export declare function DialogContent({ children, className }: {
34
34
  children: React.ReactNode;
35
35
  className?: string;
36
36
  }): import("react/jsx-runtime").JSX.Element;
37
+ export declare function DialogBody({ children, className, style }: {
38
+ children: React.ReactNode;
39
+ className?: string;
40
+ style?: React.CSSProperties;
41
+ }): import("react/jsx-runtime").JSX.Element;
42
+ export declare function DialogFooter({ children, className, style }: {
43
+ children: React.ReactNode;
44
+ className?: string;
45
+ style?: React.CSSProperties;
46
+ }): import("react/jsx-runtime").JSX.Element;
37
47
  export declare function DialogHeader({ children, className, style }: {
38
48
  children: React.ReactNode;
39
49
  className?: string;
@@ -41,7 +41,7 @@ export declare function useMobileRegistration(options?: UseMobileRegistrationOpt
41
41
  error_type?: undefined;
42
42
  } | {
43
43
  success: boolean;
44
- error_type: string | undefined;
44
+ error_type: any;
45
45
  otp_code_dev?: undefined;
46
46
  otp_method?: undefined;
47
47
  otp_sent_to?: undefined;