@ollaid/native-sso 2.1.3 → 2.1.5

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
@@ -638,7 +638,7 @@ Retourne un **token opaque chiffré** (`encrypted_credentials`) contenant les cr
638
638
  ```
639
639
 
640
640
  **Principe :**
641
- Le SaaS chiffre `app_key + secret_key + timestamp` en AES-256-CBC en utilisant la `secret_key` elle-même comme clé de chiffrement (SHA-256 → 32 bytes).
641
+ 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
642
  Le frontend transporte le blob opaque + l'`app_key` en clair (non sensible) vers l'IAM.
643
643
  L'IAM retrouve la `secret_key` via l'`app_key` dans sa table `applications`, puis déchiffre le blob.
644
644
 
@@ -648,24 +648,26 @@ L'IAM retrouve la `secret_key` via l'`app_key` dans sa table `applications`, pui
648
648
  Route::get('/native/config', function () {
649
649
  $appKey = config('services.iam.app_key');
650
650
  $secretKey = config('services.iam.secret_key');
651
+ $iamApiUrl = config('services.iam.api_url');
651
652
 
652
653
  // Clé AES = SHA-256 de la secret_key (32 bytes binaires)
653
- $aesKey = hash('sha256', $secretKey, true);
654
+ $encryptionKey = hash('sha256', $secretKey, true);
654
655
  $iv = random_bytes(16);
655
656
 
656
657
  $payload = json_encode([
657
- 'app_key' => $appKey,
658
658
  'secret_key' => $secretKey,
659
659
  'ts' => time(),
660
660
  ]);
661
661
 
662
- $encrypted = openssl_encrypt($payload, 'aes-256-cbc', $aesKey, 0, $iv);
662
+ $encrypted = openssl_encrypt($payload, 'AES-256-CBC', $encryptionKey, OPENSSL_RAW_DATA, $iv);
663
663
 
664
664
  return response()->json([
665
- 'success' => true,
666
- 'app_key' => $appKey, // En clair (non sensible, sert d'identifiant)
667
- 'encrypted_credentials' => base64_encode($iv . '::' . $encrypted),
668
- 'debug' => (bool) config('services.iam.debug'),
665
+ 'success' => true,
666
+ 'app_key' => $appKey, // En clair (non sensible, sert d'identifiant)
667
+ 'encrypted_credentials' => base64_encode($iv . '::' . base64_encode($encrypted)),
668
+ 'iam_api_url' => $iamApiUrl,
669
+ 'credentials_ttl' => 300,
670
+ 'debug' => (bool) config('services.iam.debug'),
669
671
  ]);
670
672
  });
671
673
  ```
@@ -680,12 +682,12 @@ if (!$app) {
680
682
  }
681
683
 
682
684
  // 2. Déchiffrer avec la secret_key de l'application
683
- $aesKey = hash('sha256', $app->secret_key, true); // secret_key = colonne DB
685
+ $encryptionKey = hash('sha256', $app->secret_key, true);
684
686
  $decoded = base64_decode($request->encrypted_credentials);
685
- [$iv, $ciphertext] = explode('::', $decoded, 2);
687
+ [$iv, $ciphertextB64] = explode('::', $decoded, 2);
686
688
 
687
689
  $payload = json_decode(
688
- openssl_decrypt($ciphertext, 'aes-256-cbc', $aesKey, 0, $iv),
690
+ openssl_decrypt(base64_decode($ciphertextB64), 'AES-256-CBC', $encryptionKey, OPENSSL_RAW_DATA, $iv),
689
691
  true
690
692
  );
691
693
 
@@ -694,7 +696,6 @@ if (!$payload || time() - $payload['ts'] > 300) {
694
696
  return response()->json(['success' => false, 'message' => 'Credentials expirés ou invalides'], 401);
695
697
  }
696
698
 
697
- $appKey = $payload['app_key'];
698
699
  $secretKey = $payload['secret_key'];
699
700
  // ... valider et continuer le flux
700
701
  ```
@@ -721,8 +722,8 @@ $secretKey = $payload['secret_key'];
721
722
 
722
723
  **Logique backend :**
723
724
  1. Recevoir le `callback_token`
724
- 2. Appeler l'IAM `POST /api/iam/auth/decrypt` avec `app_key`, `secret_key` et le `callback_token` (remplacer les espaces par `+`)
725
- 3. L'IAM retourne les `user_infos` (9 champs standardisés)
725
+ 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
726
+ 3. L'IAM retourne les `user_infos` (9 champs standardisés) + `app_access_token_ref`
726
727
  4. Créer ou mettre à jour l'utilisateur local
727
728
  5. Générer un token Sanctum
728
729
  6. Retourner le token + user
@@ -753,16 +754,14 @@ $secretKey = $payload['secret_key'];
753
754
  ```php
754
755
  // routes/api.php
755
756
  Route::post('/native/exchange', function (Request $request) {
756
- $callbackToken = $request->input('callback_token');
757
-
758
- // ⚠️ IMPORTANT : Remplacer les espaces par '+' avant décryptage
759
- $callbackToken = str_replace(' ', '+', $callbackToken);
757
+ $iamApiUrl = config('services.iam.api_url');
760
758
 
761
- // Appel IAM pour décrypter
762
- $response = Http::post(config('services.iam.base_url') . '/iam/auth/decrypt', [
763
- 'app_key' => config('services.iam.app_key'),
764
- 'secret_key' => config('services.iam.secret_key'),
765
- 'callback_token' => $callbackToken,
759
+ // Appel IAM pour décrypter — credentials dans les HEADERS
760
+ $response = Http::withHeaders([
761
+ 'X-IAM-App-Key' => config('services.iam.app_key'),
762
+ 'X-IAM-Secret-Key' => config('services.iam.secret_key'),
763
+ ])->post("{$iamApiUrl}/iam/auth/decrypt", [
764
+ 'token' => $request->callback_token,
766
765
  ]);
767
766
 
768
767
  if (!$response->successful() || !$response->json('success')) {
@@ -773,7 +772,9 @@ Route::post('/native/exchange', function (Request $request) {
773
772
  ], 401);
774
773
  }
775
774
 
776
- $userInfos = $response->json('user_infos');
775
+ $data = $response->json();
776
+ $userInfos = $data['user_infos'];
777
+ $appAccessTokenRef = $data['app_access_token_ref'] ?? null;
777
778
 
778
779
  // Créer ou mettre à jour l'utilisateur local
779
780
  $user = User::updateOrCreate(
@@ -786,16 +787,13 @@ Route::post('/native/exchange', function (Request $request) {
786
787
  'image' => $userInfos['image_url'] ?? null,
787
788
  'alias_reference' => $userInfos['alias_reference'],
788
789
  'user_infos' => json_encode($userInfos),
789
- 'password' => bcrypt(Str::random(32)), // Mot de passe aléatoire
790
+ 'password' => bcrypt(Str::random(32)),
790
791
  ]
791
792
  );
792
793
 
793
794
  // Générer un token Sanctum
794
795
  $token = $user->createToken('native-sso')->plainTextToken;
795
796
 
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
797
  return response()->json([
800
798
  'success' => true,
801
799
  'token' => $token,
@@ -1290,7 +1288,7 @@ Ajoutez ces variables dans le fichier `.env` du backend SaaS :
1290
1288
  # Credentials IAM (récupérés depuis le dashboard iam.ollaid.com)
1291
1289
  IAM_APP_KEY=oiam_ak_xxxxxxxxxxxxxxxxxxxxxxxx
1292
1290
  IAM_SECRET_KEY=oiam_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
1293
- IAM_BASE_URL=https://identityam.ollaid.com/api
1291
+ IAM_API_URL=https://identityam.ollaid.com/api
1294
1292
 
1295
1293
  # Mode debug — contrôle le DebugPanel et les logs côté frontend
1296
1294
  # true : active le DebugPanel + logs console + détails d'erreur dans les réponses JSON
@@ -1307,7 +1305,7 @@ Et dans `config/services.php` :
1307
1305
  'iam' => [
1308
1306
  'app_key' => env('IAM_APP_KEY'),
1309
1307
  'secret_key' => env('IAM_SECRET_KEY'),
1310
- 'base_url' => env('IAM_BASE_URL', 'https://identityam.ollaid.com/api'),
1308
+ 'api_url' => env('IAM_API_URL', 'https://identityam.ollaid.com/api'),
1311
1309
  'debug' => env('IAM_DEBUG', false), // Active le debug côté frontend (false si absent)
1312
1310
  ],
1313
1311
  ```
@@ -2,7 +2,7 @@
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 1.0.0
5
+ * @version 2.1.4
6
6
  */
7
7
  interface DebugPanelProps {
8
8
  saasApiUrl: string;
@@ -2,7 +2,7 @@
2
2
  * Login Modal for @ollaid/native-sso
3
3
  * Complete login flow aligned with web SSO /sso/auth design
4
4
  *
5
- * @version 1.0.0
5
+ * @version 2.1.4
6
6
  */
7
7
  import type { UserInfos } from '../types/native';
8
8
  export interface LoginModalProps {
@@ -2,7 +2,7 @@
2
2
  * NativeSSOPage — Page autonome complète pour @ollaid/native-sso
3
3
  * Design aligné sur /sso/auth (fond primary, card blanche, ShieldCheck branding)
4
4
  *
5
- * @version 2.0.0
5
+ * @version 2.1.4
6
6
  */
7
7
  import type { UserInfos } from '../types/native';
8
8
  export interface NativeSSOPageProps {
@@ -2,7 +2,7 @@
2
2
  * OnboardingModal — Post-login modal for missing profile info
3
3
  * Asks for photo + phone if not present in user_infos
4
4
  *
5
- * @version 1.0.0
5
+ * @version 2.1.4
6
6
  */
7
7
  import type { NativeUser } from '../types/native';
8
8
  export interface OnboardingModalProps {
@@ -3,7 +3,7 @@
3
3
  * Flow: email → method-choice → OTP → new password → success
4
4
  * Design aligned with web SSO
5
5
  *
6
- * @version 1.0.0
6
+ * @version 2.1.4
7
7
  */
8
8
  export interface PasswordRecoveryModalProps {
9
9
  open: boolean;
@@ -2,7 +2,7 @@
2
2
  * Signup Modal for @ollaid/native-sso — Design aligned with web SSO
3
3
  * Full signup flow: intro → account-type → info → OTP → password → confirm → success
4
4
  *
5
- * @version 1.0.0
5
+ * @version 2.1.4
6
6
  */
7
7
  import type { UserInfos } from '../types/native';
8
8
  export interface SignupModalProps {
@@ -3,7 +3,7 @@
3
3
  * Lightweight replacements for shadcn/ui components + inline SVG icons
4
4
  * No external dependencies required
5
5
  *
6
- * @version 1.0.0
6
+ * @version 2.1.4
7
7
  */
8
8
  import React from 'react';
9
9
  export declare function IconShieldCheck(props: React.SVGProps<SVGSVGElement>): import("react/jsx-runtime").JSX.Element;
@@ -22,7 +22,7 @@
22
22
  * };
23
23
  * ```
24
24
  *
25
- * @version 1.0.0
25
+ * @version 2.1.4
26
26
  */
27
27
  export interface UseLogoutOptions {
28
28
  /** Callback appelé après une déconnexion réussie (redirection, toast, etc.) */
@@ -2,7 +2,7 @@
2
2
  * Hook de récupération de mot de passe v1.0
3
3
  * Architecture Frontend-First avec appels directs à l'IAM
4
4
  *
5
- * @version 1.0.0
5
+ * @version 2.1.4
6
6
  */
7
7
  export interface UseMobilePasswordOptions {
8
8
  saasApiUrl: string;
@@ -2,7 +2,7 @@
2
2
  * Hook d'inscription Mobile SSO v1.0
3
3
  * Gère le flow: init → verify-otp → complete
4
4
  *
5
- * @version 1.0.0
5
+ * @version 2.1.4
6
6
  */
7
7
  import type { MobileRegistrationFormData, AccountType } from '../types/mobile';
8
8
  interface RegistrationConflict {
@@ -2,7 +2,7 @@
2
2
  * Hook d'authentification Native SSO v1.0
3
3
  * Architecture Frontend-First avec appels directs à l'IAM
4
4
  *
5
- * @version 1.0.0
5
+ * @version 2.1.4
6
6
  */
7
7
  import type { NativeAuthStatus, NativeExchangeResponse, AccountType } from '../types/native';
8
8
  export interface UseNativeAuthOptions {
@@ -10,7 +10,7 @@
10
10
  * - Si 401 → révoque l'IAM (POST /iam/disconnect) + nettoie le frontend
11
11
  * - Ne déconnecte PAS si offline ou serveur inaccessible
12
12
  *
13
- * @version 2.0.0
13
+ * @version 2.1.4
14
14
  */
15
15
  import type { UserInfos } from '../types/native';
16
16
  export interface UseTokenHealthCheckOptions {
package/dist/index.cjs CHANGED
@@ -2529,17 +2529,11 @@ function LoginModal({
2529
2529
  setPhone(initialPhone);
2530
2530
  }
2531
2531
  }, [open, initialPhone]);
2532
- const exchangeCallbackToken = async (callbackToken) => {
2533
- try {
2534
- const response = await nativeAuthService.exchange(callbackToken);
2535
- if (response.success && response.token) {
2536
- setLoginData({ token: response.token, user: response.user });
2537
- setLoginSuccess(true);
2538
- } else {
2539
- setLocalError("Erreur lors de la finalisation de la connexion");
2540
- }
2541
- } catch {
2542
- setLocalError("Erreur de connexion au serveur");
2532
+ const finalizeLogin = (result) => {
2533
+ if (result.success && result.user) {
2534
+ const token = localStorage.getItem("native_auth_token") || localStorage.getItem("auth_token") || "";
2535
+ setLoginData({ token, user: result.user });
2536
+ setLoginSuccess(true);
2543
2537
  }
2544
2538
  };
2545
2539
  const handleEmailCheck = async () => {
@@ -2569,7 +2563,7 @@ function LoginModal({
2569
2563
  return;
2570
2564
  }
2571
2565
  const result = await submitPassword(password);
2572
- if (result.success && result.callback_token) await exchangeCallbackToken(result.callback_token);
2566
+ finalizeLogin(result);
2573
2567
  };
2574
2568
  const handleEmailOtpVerify = async () => {
2575
2569
  setLocalError(null);
@@ -2579,7 +2573,7 @@ function LoginModal({
2579
2573
  return;
2580
2574
  }
2581
2575
  const result = await submitOtp(emailOtpCode);
2582
- if (result.success && result.callback_token) await exchangeCallbackToken(result.callback_token);
2576
+ finalizeLogin(result);
2583
2577
  };
2584
2578
  const handlePhoneInit = async () => {
2585
2579
  setLocalError(null);
@@ -2599,7 +2593,7 @@ function LoginModal({
2599
2593
  return;
2600
2594
  }
2601
2595
  const result = await submitOtp(phoneOtpCode);
2602
- if (result.success && result.callback_token) await exchangeCallbackToken(result.callback_token);
2596
+ finalizeLogin(result);
2603
2597
  };
2604
2598
  const handleAccessOtpSubmit = async () => {
2605
2599
  setLocalError(null);
@@ -2609,17 +2603,13 @@ function LoginModal({
2609
2603
  return;
2610
2604
  }
2611
2605
  const result = await loginWithAccessOtp(accessOtpCode);
2612
- if (result.success) {
2613
- if (result.callback_token) {
2614
- await exchangeCallbackToken(result.callback_token);
2615
- }
2616
- }
2606
+ finalizeLogin(result);
2617
2607
  };
2618
2608
  const handleGrantAccess = async () => {
2619
2609
  setLocalError(null);
2620
2610
  clearError();
2621
2611
  const result = await grantAccess();
2622
- if (result.success && result.callback_token) await exchangeCallbackToken(result.callback_token);
2612
+ finalizeLogin(result);
2623
2613
  };
2624
2614
  const handleResendOTP = async () => {
2625
2615
  if (resendCooldown > 0) return;