@ollaid/native-sso 2.1.2 → 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
@@ -359,13 +359,14 @@ class NativeConfigController extends Controller
359
359
  $payload = json_encode(['secret_key' => $secretKey, 'ts' => time()]);
360
360
  $key = hash('sha256', $secretKey, true);
361
361
  $iv = random_bytes(16);
362
- $encrypted = openssl_encrypt($payload, 'AES-256-CBC', $key, 0, $iv);
363
- $encryptedCredentials = base64_encode($iv . '::' . $encrypted);
362
+ $encrypted = openssl_encrypt($payload, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
363
+ $encryptedCredentials = base64_encode($iv . '::' . base64_encode($encrypted));
364
364
 
365
365
  return response()->json([
366
366
  'success' => true,
367
367
  'app_key' => $appKey,
368
368
  'encrypted_credentials' => $encryptedCredentials,
369
+ 'iam_api_url' => config("services.{$prefix}.api_url", 'https://identityam.ollaid.com/api'),
369
370
  'credentials_ttl' => 300,
370
371
  'debug' => $debug,
371
372
  ]);
@@ -637,7 +638,7 @@ Retourne un **token opaque chiffré** (`encrypted_credentials`) contenant les cr
637
638
  ```
638
639
 
639
640
  **Principe :**
640
- 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).
641
642
  Le frontend transporte le blob opaque + l'`app_key` en clair (non sensible) vers l'IAM.
642
643
  L'IAM retrouve la `secret_key` via l'`app_key` dans sa table `applications`, puis déchiffre le blob.
643
644
 
@@ -647,24 +648,26 @@ L'IAM retrouve la `secret_key` via l'`app_key` dans sa table `applications`, pui
647
648
  Route::get('/native/config', function () {
648
649
  $appKey = config('services.iam.app_key');
649
650
  $secretKey = config('services.iam.secret_key');
651
+ $iamApiUrl = config('services.iam.api_url');
650
652
 
651
653
  // Clé AES = SHA-256 de la secret_key (32 bytes binaires)
652
- $aesKey = hash('sha256', $secretKey, true);
654
+ $encryptionKey = hash('sha256', $secretKey, true);
653
655
  $iv = random_bytes(16);
654
656
 
655
657
  $payload = json_encode([
656
- 'app_key' => $appKey,
657
658
  'secret_key' => $secretKey,
658
659
  'ts' => time(),
659
660
  ]);
660
661
 
661
- $encrypted = openssl_encrypt($payload, 'aes-256-cbc', $aesKey, 0, $iv);
662
+ $encrypted = openssl_encrypt($payload, 'AES-256-CBC', $encryptionKey, OPENSSL_RAW_DATA, $iv);
662
663
 
663
664
  return response()->json([
664
- 'success' => true,
665
- 'app_key' => $appKey, // En clair (non sensible, sert d'identifiant)
666
- 'encrypted_credentials' => base64_encode($iv . '::' . $encrypted),
667
- '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'),
668
671
  ]);
669
672
  });
670
673
  ```
@@ -679,12 +682,12 @@ if (!$app) {
679
682
  }
680
683
 
681
684
  // 2. Déchiffrer avec la secret_key de l'application
682
- $aesKey = hash('sha256', $app->secret_key, true); // secret_key = colonne DB
685
+ $encryptionKey = hash('sha256', $app->secret_key, true);
683
686
  $decoded = base64_decode($request->encrypted_credentials);
684
- [$iv, $ciphertext] = explode('::', $decoded, 2);
687
+ [$iv, $ciphertextB64] = explode('::', $decoded, 2);
685
688
 
686
689
  $payload = json_decode(
687
- openssl_decrypt($ciphertext, 'aes-256-cbc', $aesKey, 0, $iv),
690
+ openssl_decrypt(base64_decode($ciphertextB64), 'AES-256-CBC', $encryptionKey, OPENSSL_RAW_DATA, $iv),
688
691
  true
689
692
  );
690
693
 
@@ -693,7 +696,6 @@ if (!$payload || time() - $payload['ts'] > 300) {
693
696
  return response()->json(['success' => false, 'message' => 'Credentials expirés ou invalides'], 401);
694
697
  }
695
698
 
696
- $appKey = $payload['app_key'];
697
699
  $secretKey = $payload['secret_key'];
698
700
  // ... valider et continuer le flux
699
701
  ```
@@ -720,8 +722,8 @@ $secretKey = $payload['secret_key'];
720
722
 
721
723
  **Logique backend :**
722
724
  1. Recevoir le `callback_token`
723
- 2. Appeler l'IAM `POST /api/iam/auth/decrypt` avec `app_key`, `secret_key` et le `callback_token` (remplacer les espaces par `+`)
724
- 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`
725
727
  4. Créer ou mettre à jour l'utilisateur local
726
728
  5. Générer un token Sanctum
727
729
  6. Retourner le token + user
@@ -752,16 +754,14 @@ $secretKey = $payload['secret_key'];
752
754
  ```php
753
755
  // routes/api.php
754
756
  Route::post('/native/exchange', function (Request $request) {
755
- $callbackToken = $request->input('callback_token');
756
-
757
- // ⚠️ IMPORTANT : Remplacer les espaces par '+' avant décryptage
758
- $callbackToken = str_replace(' ', '+', $callbackToken);
757
+ $iamApiUrl = config('services.iam.api_url');
759
758
 
760
- // Appel IAM pour décrypter
761
- $response = Http::post(config('services.iam.base_url') . '/iam/auth/decrypt', [
762
- 'app_key' => config('services.iam.app_key'),
763
- 'secret_key' => config('services.iam.secret_key'),
764
- '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,
765
765
  ]);
766
766
 
767
767
  if (!$response->successful() || !$response->json('success')) {
@@ -772,7 +772,9 @@ Route::post('/native/exchange', function (Request $request) {
772
772
  ], 401);
773
773
  }
774
774
 
775
- $userInfos = $response->json('user_infos');
775
+ $data = $response->json();
776
+ $userInfos = $data['user_infos'];
777
+ $appAccessTokenRef = $data['app_access_token_ref'] ?? null;
776
778
 
777
779
  // Créer ou mettre à jour l'utilisateur local
778
780
  $user = User::updateOrCreate(
@@ -785,16 +787,13 @@ Route::post('/native/exchange', function (Request $request) {
785
787
  'image' => $userInfos['image_url'] ?? null,
786
788
  'alias_reference' => $userInfos['alias_reference'],
787
789
  'user_infos' => json_encode($userInfos),
788
- 'password' => bcrypt(Str::random(32)), // Mot de passe aléatoire
790
+ 'password' => bcrypt(Str::random(32)),
789
791
  ]
790
792
  );
791
793
 
792
794
  // Générer un token Sanctum
793
795
  $token = $user->createToken('native-sso')->plainTextToken;
794
796
 
795
- // Récupérer app_access_token_ref depuis la réponse IAM
796
- $appAccessTokenRef = $response->json('user_infos.app_access_token_ref');
797
-
798
797
  return response()->json([
799
798
  'success' => true,
800
799
  'token' => $token,
@@ -944,17 +943,19 @@ class NativeAuthController extends Controller
944
943
  $aesKey = hash('sha256', $secretKey, true);
945
944
  $iv = random_bytes(16);
946
945
  $payload = json_encode([
947
- 'app_key' => $appKey,
948
946
  'secret_key' => $secretKey,
949
947
  'ts' => time(),
950
948
  ]);
951
949
 
952
- $encrypted = openssl_encrypt($payload, 'aes-256-cbc', $aesKey, 0, $iv);
950
+ $encrypted = openssl_encrypt($payload, 'aes-256-cbc', $aesKey, OPENSSL_RAW_DATA, $iv);
951
+ $encryptedCredentials = base64_encode($iv . '::' . base64_encode($encrypted));
953
952
 
954
953
  return response()->json([
955
954
  'success' => true,
956
955
  'app_key' => $appKey, // En clair (non sensible, sert d'identifiant pour l'IAM)
957
- 'encrypted_credentials' => base64_encode($iv . '::' . $encrypted),
956
+ 'encrypted_credentials' => $encryptedCredentials,
957
+ 'iam_api_url' => config('services.iam.api_url', 'https://identityam.ollaid.com/api'),
958
+ 'credentials_ttl' => 300,
958
959
  'debug' => (bool) config('services.iam.debug'), // Contrôlé par IAM_DEBUG dans .env
959
960
  ]);
960
961
  }
@@ -983,18 +984,17 @@ class NativeAuthController extends Controller
983
984
  ], 400);
984
985
  }
985
986
 
986
- // ⚠️ IMPORTANT : Remplacer les espaces par '+' (encodage URL)
987
- $callbackToken = str_replace(' ', '+', $callbackToken);
988
-
989
987
  try {
990
- $response = Http::timeout(30)->post(
991
- config('services.iam.base_url') . '/iam/auth/decrypt',
992
- [
993
- 'app_key' => config('services.iam.app_key'),
994
- 'secret_key' => config('services.iam.secret_key'),
995
- 'callback_token' => $callbackToken,
996
- ]
997
- );
988
+ $response = Http::timeout(30)
989
+ ->withHeaders([
990
+ 'Content-Type' => 'application/json',
991
+ 'Accept' => 'application/json',
992
+ 'X-IAM-App-Key' => config('services.iam.app_key'),
993
+ 'X-IAM-Secret-Key' => config('services.iam.secret_key'),
994
+ ])
995
+ ->post(config('services.iam.api_url') . '/iam/auth/decrypt', [
996
+ 'token' => $callbackToken,
997
+ ]);
998
998
 
999
999
  if (!$response->successful() || !$response->json('success')) {
1000
1000
  $error = $response->json();
@@ -1052,8 +1052,15 @@ class NativeAuthController extends Controller
1052
1052
  $expiresAt = now()->addDays(30);
1053
1053
  $token = $user->createToken('native-sso', ['*'], $expiresAt);
1054
1054
 
1055
- // Récupérer app_access_token_ref depuis la réponse IAM
1056
- $appAccessTokenRef = $userInfos['app_access_token_ref'] ?? null;
1055
+ // Récupérer app_access_token_ref depuis la racine de la réponse IAM
1056
+ $appAccessTokenRef = $data['app_access_token_ref'] ?? null;
1057
+
1058
+ // Stocker la ref dans le token Sanctum pour le webhook de révocation
1059
+ if ($appAccessTokenRef) {
1060
+ $token->accessToken->forceFill([
1061
+ 'app_access_token_ref' => $appAccessTokenRef,
1062
+ ])->save();
1063
+ }
1057
1064
 
1058
1065
  return response()->json([
1059
1066
  'success' => true,
@@ -1115,8 +1122,9 @@ class NativeAuthController extends Controller
1115
1122
  $user = $request->user();
1116
1123
 
1117
1124
  return response()->json([
1118
- 'status' => 'connected',
1119
- 'user' => [
1125
+ 'status' => 'connected',
1126
+ 'message' => 'Utilisateur connecté',
1127
+ 'user' => [
1120
1128
  'name' => $user->name,
1121
1129
  'email' => $user->email,
1122
1130
  'ccphone' => $user->ccphone,
@@ -1145,14 +1153,17 @@ class NativeAuthController extends Controller
1145
1153
 
1146
1154
  if ($user) {
1147
1155
  $sanctumTokenPlain = $request->bearerToken();
1148
- $user->currentAccessToken()?->delete();
1156
+ $currentToken = $user->currentAccessToken();
1157
+ $appAccessTokenRef = $currentToken?->app_access_token_ref ?? null;
1158
+
1159
+ // Supprimer le token APRÈS avoir lu la ref
1160
+ $currentToken?->delete();
1149
1161
 
1150
1162
  // Notifier l'IAM (fire-and-forget, timeout 5s)
1151
- if ($sanctumTokenPlain) {
1163
+ if ($sanctumTokenPlain || $appAccessTokenRef) {
1152
1164
  $iamPrefix = $request->attributes->get('iam_prefix', 'iam');
1153
1165
  $iamApiUrl = config("services.{$iamPrefix}.api_url", 'https://identityam.ollaid.com/api');
1154
1166
  try {
1155
- $appAccessTokenRef = $user->currentAccessToken()->app_access_token_ref ?? null;
1156
1167
  Http::timeout(5)->post("{$iamApiUrl}/iam/disconnect", array_filter([
1157
1168
  'sanctum_token' => $sanctumTokenPlain,
1158
1169
  'app_access_token_ref' => $appAccessTokenRef,
@@ -1277,7 +1288,7 @@ Ajoutez ces variables dans le fichier `.env` du backend SaaS :
1277
1288
  # Credentials IAM (récupérés depuis le dashboard iam.ollaid.com)
1278
1289
  IAM_APP_KEY=oiam_ak_xxxxxxxxxxxxxxxxxxxxxxxx
1279
1290
  IAM_SECRET_KEY=oiam_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
1280
- IAM_BASE_URL=https://identityam.ollaid.com/api
1291
+ IAM_API_URL=https://identityam.ollaid.com/api
1281
1292
 
1282
1293
  # Mode debug — contrôle le DebugPanel et les logs côté frontend
1283
1294
  # true : active le DebugPanel + logs console + détails d'erreur dans les réponses JSON
@@ -1294,7 +1305,7 @@ Et dans `config/services.php` :
1294
1305
  'iam' => [
1295
1306
  'app_key' => env('IAM_APP_KEY'),
1296
1307
  'secret_key' => env('IAM_SECRET_KEY'),
1297
- 'base_url' => env('IAM_BASE_URL', 'https://identityam.ollaid.com/api'),
1308
+ 'api_url' => env('IAM_API_URL', 'https://identityam.ollaid.com/api'),
1298
1309
  'debug' => env('IAM_DEBUG', false), // Active le debug côté frontend (false si absent)
1299
1310
  ],
1300
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;