@ollaid/native-sso 2.8.2 → 2.8.3
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/docs/10_login-redirect.md +103 -0
- package/docs/11_session-refresh-resilience.md +130 -0
- package/docs/12_login_mode.md +55 -0
- package/docs/13_must_have_working.md +60 -0
- package/docs/14_backend_env_key.md +50 -0
- package/docs/1_quick-start.md +87 -0
- package/docs/2_backend-contract.md +199 -0
- package/docs/3_storage-security.md +54 -0
- package/docs/4_webhooks.md +48 -0
- package/docs/5_iam-account.md +190 -0
- package/docs/6_advanced-services.md +115 -0
- package/docs/7_migration-notes.md +16 -0
- package/docs/8_update_infos.md +139 -0
- package/docs/9_password_magic_link.md +84 -0
- package/docs/README.md +66 -0
- package/package.json +6 -3
- package/scripts/copy-docs.mjs +36 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Storage & Security
|
|
2
|
+
|
|
3
|
+
Version: `2.8.3`
|
|
4
|
+
|
|
5
|
+
## Ce que fait le package
|
|
6
|
+
|
|
7
|
+
- Les données de session sont stockées avec des clés préfixées `sso_`.
|
|
8
|
+
- Le contenu persistant est chiffré au repos par défaut.
|
|
9
|
+
- Le frontend hôte lit la session via `getSsoSessionSnapshot()`.
|
|
10
|
+
- Le package gère aussi des clés internes de rappel profil `sso_image_*` pour l'onboarding et le snooze.
|
|
11
|
+
|
|
12
|
+
### Exemple de lecture
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { getSsoSessionSnapshot } from '@ollaid/native-sso';
|
|
16
|
+
|
|
17
|
+
const session = getSsoSessionSnapshot();
|
|
18
|
+
|
|
19
|
+
console.log(session.authToken);
|
|
20
|
+
console.log(session.user);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Ce que le SaaS doit retenir
|
|
24
|
+
|
|
25
|
+
- Le SaaS ne lit pas le storage directement.
|
|
26
|
+
- Le SaaS demande la session au package quand il en a besoin.
|
|
27
|
+
- Les helpers `getAuthToken()`, `getAuthUser()` et `getAccountType()` restent compatibles, mais l’API recommandée est `getSsoSessionSnapshot()`.
|
|
28
|
+
- Si le backend fournit `refresh_token` / `refresh_expires_at`, le package les persiste et peut tenter un refresh avant déconnexion.
|
|
29
|
+
|
|
30
|
+
### Quand utiliser quoi
|
|
31
|
+
|
|
32
|
+
- `getSsoSessionSnapshot()` : lecture standard côté frontend SaaS.
|
|
33
|
+
- `getAuthToken()` : compatibilité rapide.
|
|
34
|
+
- `getAuthUser()` : compatibilité rapide.
|
|
35
|
+
- `getAccountType()` : compatibilité rapide.
|
|
36
|
+
|
|
37
|
+
## Recommandation
|
|
38
|
+
|
|
39
|
+
- **Web**: storage chiffré du package ou session serveur selon votre architecture.
|
|
40
|
+
- **Capacitor**: fournissez un storage natif sécurisé si vous pouvez.
|
|
41
|
+
- **Logout**: utilisez toujours `logout()`.
|
|
42
|
+
|
|
43
|
+
## Refresh token et persistance
|
|
44
|
+
|
|
45
|
+
- Si le backend fournit `refresh_token` / `refresh_expires_at`, le package les persiste et peut tenter un refresh avant déconnexion.
|
|
46
|
+
- Le backend SaaS doit conserver un `refresh_token_hash` côté base pour que la rotation fonctionne.
|
|
47
|
+
- Une erreur réseau sur `refresh()` ne doit pas vider la session. Voir [Session Refresh & Resilience](./11_session-refresh-resilience.md).
|
|
48
|
+
|
|
49
|
+
## Anti-patterns
|
|
50
|
+
|
|
51
|
+
- Ne pas lire `localStorage` directement depuis le frontend hôte.
|
|
52
|
+
- Ne pas utiliser `clearAuthToken()` comme flux principal.
|
|
53
|
+
- Ne pas effacer le storage manuellement pour simuler une déconnexion.
|
|
54
|
+
- Ne pas compter sur le chiffrement local comme protection absolue contre du JavaScript compromis.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Webhooks
|
|
2
|
+
|
|
3
|
+
Version: `2.8.3`
|
|
4
|
+
|
|
5
|
+
## But
|
|
6
|
+
|
|
7
|
+
Le backend IAM envoie des webhooks pour prévenir le SaaS d’une révocation, d’une suspension ou d’une mise à jour utilisateur.
|
|
8
|
+
|
|
9
|
+
## Points clés
|
|
10
|
+
|
|
11
|
+
- Vérifiez `X-IAM-Signature`.
|
|
12
|
+
- Dédupliquez `X-IAM-Webhook-Id`.
|
|
13
|
+
- Révoquez uniquement la session ciblée.
|
|
14
|
+
|
|
15
|
+
## Exemple de payload
|
|
16
|
+
|
|
17
|
+
Headers:
|
|
18
|
+
|
|
19
|
+
```http
|
|
20
|
+
X-IAM-Signature: sha256=...
|
|
21
|
+
X-IAM-Webhook-Id: wh_123
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Body:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"type": "session",
|
|
29
|
+
"event": "revoked",
|
|
30
|
+
"timestamp": "2026-05-15T17:00:00Z",
|
|
31
|
+
"data": {
|
|
32
|
+
"iam_reference": "USR-XXXX",
|
|
33
|
+
"app_access_token_ref": "aat_xxx"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Événements utiles
|
|
39
|
+
|
|
40
|
+
- `session.revoked`
|
|
41
|
+
- `access.revoked`
|
|
42
|
+
- `user.suspended`
|
|
43
|
+
- `user.updated`
|
|
44
|
+
|
|
45
|
+
## Règle pratique
|
|
46
|
+
|
|
47
|
+
- Ne faites pas de révocation globale par défaut.
|
|
48
|
+
- Utilisez `app_access_token_ref` pour cibler la session.
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# IAM Account
|
|
2
|
+
|
|
3
|
+
Version: `2.8.3`
|
|
4
|
+
|
|
5
|
+
Les APIs IAM Account servent à synchroniser des données utilisateur entre un backend SaaS et le backend IAM.
|
|
6
|
+
|
|
7
|
+
## Règle
|
|
8
|
+
|
|
9
|
+
- Ces APIs sont **server-to-server uniquement**.
|
|
10
|
+
- N’appelez jamais ces méthodes depuis le frontend navigateur.
|
|
11
|
+
- Utilisez toujours `app_key` + `secret_key` côté backend.
|
|
12
|
+
|
|
13
|
+
## API exposée
|
|
14
|
+
|
|
15
|
+
Le package exporte `iamAccountService` avec les méthodes suivantes:
|
|
16
|
+
|
|
17
|
+
- `linkPhone()`
|
|
18
|
+
- `linkEmail()`
|
|
19
|
+
- `refreshUserInfo()`
|
|
20
|
+
- `refreshUserInfoBulk()`
|
|
21
|
+
- `updateAvatar()`
|
|
22
|
+
- `resetAvatar()`
|
|
23
|
+
|
|
24
|
+
## Références backend
|
|
25
|
+
|
|
26
|
+
- `POST /api/iam/link-phone`
|
|
27
|
+
- `POST /api/iam/link-email`
|
|
28
|
+
- `POST /api/iam/refresh-user-info`
|
|
29
|
+
- `POST /api/iam/update-avatar`
|
|
30
|
+
- `POST /api/iam/reset-avatar`
|
|
31
|
+
|
|
32
|
+
## `linkEmail()`
|
|
33
|
+
|
|
34
|
+
Liaison d’un email à un utilisateur IAM existant.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { iamAccountService } from '@ollaid/native-sso';
|
|
38
|
+
|
|
39
|
+
const result = await iamAccountService.linkEmail({
|
|
40
|
+
app_key: 'oiam_ak_xxx',
|
|
41
|
+
secret_key: 'sk_xxx',
|
|
42
|
+
iam_reference: 'USR-XXXX',
|
|
43
|
+
email: 'john@example.com',
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Réponse:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"success": true,
|
|
52
|
+
"message": "Adresse email liée avec succès",
|
|
53
|
+
"user_reference": "USR-XXXX",
|
|
54
|
+
"user_infos": {
|
|
55
|
+
"name": "John Doe",
|
|
56
|
+
"email": "john@example.com",
|
|
57
|
+
"ccphone": "+221",
|
|
58
|
+
"phone": "771234567",
|
|
59
|
+
"address": "",
|
|
60
|
+
"town": "",
|
|
61
|
+
"country": "",
|
|
62
|
+
"image_url": "https://...",
|
|
63
|
+
"auth_2fa": false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## `linkPhone()`
|
|
69
|
+
|
|
70
|
+
Liaison d’un numéro de téléphone à un utilisateur IAM existant.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
await iamAccountService.linkPhone({
|
|
74
|
+
app_key: 'oiam_ak_xxx',
|
|
75
|
+
secret_key: 'sk_xxx',
|
|
76
|
+
iam_reference: 'USR-XXXX',
|
|
77
|
+
ccphone: '+221',
|
|
78
|
+
phone: '771234567',
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## `refreshUserInfo()`
|
|
83
|
+
|
|
84
|
+
Récupère `user_infos` pour un alias donné.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
await iamAccountService.refreshUserInfo({
|
|
88
|
+
secret_key: 'sk_xxx',
|
|
89
|
+
alias_reference: 'ALI-XXXX',
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Réponse single:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"success": true,
|
|
98
|
+
"user_reference": "USR-XXXX",
|
|
99
|
+
"alias_reference": "ALI-XXXX",
|
|
100
|
+
"login_type": "email",
|
|
101
|
+
"user_infos": {
|
|
102
|
+
"name": "John Doe",
|
|
103
|
+
"email": "john@example.com",
|
|
104
|
+
"ccphone": "+221",
|
|
105
|
+
"phone": "771234567",
|
|
106
|
+
"address": "",
|
|
107
|
+
"town": "",
|
|
108
|
+
"country": "",
|
|
109
|
+
"image_url": "https://...",
|
|
110
|
+
"auth_2fa": false
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## `refreshUserInfoBulk()`
|
|
116
|
+
|
|
117
|
+
Mode bulk pour synchroniser plusieurs `alias_reference`.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
await iamAccountService.refreshUserInfoBulk({
|
|
121
|
+
secret_key: 'sk_xxx',
|
|
122
|
+
alias_references: ['ALI-1', 'ALI-2'],
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Réponse:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"success": true,
|
|
131
|
+
"total_requested": 2,
|
|
132
|
+
"total_found": 1,
|
|
133
|
+
"total_errors": 1,
|
|
134
|
+
"data": [
|
|
135
|
+
{
|
|
136
|
+
"user_reference": "USR-XXXX",
|
|
137
|
+
"alias_reference": "ALI-1",
|
|
138
|
+
"login_type": "email",
|
|
139
|
+
"user_infos": {
|
|
140
|
+
"name": "John Doe",
|
|
141
|
+
"email": "john@example.com",
|
|
142
|
+
"ccphone": "+221",
|
|
143
|
+
"phone": "771234567",
|
|
144
|
+
"address": "",
|
|
145
|
+
"town": "",
|
|
146
|
+
"country": "",
|
|
147
|
+
"image_url": "https://...",
|
|
148
|
+
"auth_2fa": false
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
"errors": [
|
|
153
|
+
{
|
|
154
|
+
"alias_reference": "ALI-2",
|
|
155
|
+
"error": "Alias non trouvé"
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## `updateAvatar()`
|
|
162
|
+
|
|
163
|
+
Met à jour l’avatar d’un utilisateur sur un `AppAccess` donné.
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
await iamAccountService.updateAvatar({
|
|
167
|
+
app_key: 'oiam_ak_xxx',
|
|
168
|
+
secret_key: 'sk_xxx',
|
|
169
|
+
alias_reference: 'ALI-XXXX',
|
|
170
|
+
avatar_url: 'https://cdn.example.com/avatar.jpg',
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## `resetAvatar()`
|
|
175
|
+
|
|
176
|
+
Réinitialise l’avatar spécifique à l’application pour revenir sur la cascade standard.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
await iamAccountService.resetAvatar({
|
|
180
|
+
app_key: 'oiam_ak_xxx',
|
|
181
|
+
secret_key: 'sk_xxx',
|
|
182
|
+
alias_reference: 'ALI-XXXX',
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Sécurité
|
|
187
|
+
|
|
188
|
+
- `secret_key` ne doit jamais être exposée au frontend.
|
|
189
|
+
- Ces appels doivent être exécutés uniquement par le backend SaaS.
|
|
190
|
+
- Ces méthodes ne remplacent pas le flux SSO principal.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Advanced Services
|
|
2
|
+
|
|
3
|
+
Version: `2.8.3`
|
|
4
|
+
|
|
5
|
+
Ces helpers sont utiles pour les parcours IAM avancés, mais ils ne remplacent pas le flux SSO principal.
|
|
6
|
+
|
|
7
|
+
## Règles
|
|
8
|
+
|
|
9
|
+
- Utilisez-les seulement après authentification.
|
|
10
|
+
- Ne les appelez pas depuis du code non authentifié.
|
|
11
|
+
- Ils s’appuient sur les credentials chargés par `nativeAuthService`.
|
|
12
|
+
|
|
13
|
+
## `mobilePasswordService`
|
|
14
|
+
|
|
15
|
+
Flux de récupération de mot de passe mobile.
|
|
16
|
+
|
|
17
|
+
### `initRecovery(email)`
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { mobilePasswordService } from '@ollaid/native-sso';
|
|
21
|
+
|
|
22
|
+
const init = await mobilePasswordService.initRecovery('john@example.com');
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Réponse possible:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"success": true,
|
|
30
|
+
"process_token": "proc_xxx",
|
|
31
|
+
"status": "pending_otp",
|
|
32
|
+
"expires_at": "2026-05-15T18:00:00Z",
|
|
33
|
+
"otp_code_dev": "123456",
|
|
34
|
+
"receive_mode": "email",
|
|
35
|
+
"masked_email": "j***@example.com"
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### `selectMethod(processToken, receiveChoice)`
|
|
40
|
+
|
|
41
|
+
Choisit `email` ou `phone`.
|
|
42
|
+
|
|
43
|
+
### `reset(processToken, password)`
|
|
44
|
+
|
|
45
|
+
Termine la réinitialisation du mot de passe.
|
|
46
|
+
|
|
47
|
+
### `resendOtp(processToken)`
|
|
48
|
+
|
|
49
|
+
Renvoie l’OTP et expose parfois un délai de cooldown.
|
|
50
|
+
|
|
51
|
+
## `profileChangeService`
|
|
52
|
+
|
|
53
|
+
Flux OTP pour changer email ou téléphone.
|
|
54
|
+
|
|
55
|
+
### `requestEmailChange(newEmail)`
|
|
56
|
+
|
|
57
|
+
Demande un changement d’email avec OTP.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { profileChangeService } from '@ollaid/native-sso';
|
|
61
|
+
|
|
62
|
+
const request = await profileChangeService.requestEmailChange('new@example.com');
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Réponse possible:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"success": true,
|
|
70
|
+
"message": "Demande envoyée",
|
|
71
|
+
"request_id": 42,
|
|
72
|
+
"method": "phone",
|
|
73
|
+
"otp_dev": "654321"
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### `requestPhoneChange(ccphone, phone)`
|
|
78
|
+
|
|
79
|
+
Demande un changement de téléphone avec OTP.
|
|
80
|
+
|
|
81
|
+
### `verifyOldOTP(requestId, otpCode)` / `verifyNewOTP(requestId, otpCode)`
|
|
82
|
+
|
|
83
|
+
Valide l’ancienne ou la nouvelle valeur selon le workflow.
|
|
84
|
+
|
|
85
|
+
### `resendOTP(requestId)` / `switchMethod(requestId, method)`
|
|
86
|
+
|
|
87
|
+
Permet de renvoyer le code ou de basculer entre email et téléphone.
|
|
88
|
+
|
|
89
|
+
## `profileMediaService`
|
|
90
|
+
|
|
91
|
+
Upload de l’image de profil.
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { profileMediaService } from '@ollaid/native-sso';
|
|
95
|
+
|
|
96
|
+
await profileMediaService.uploadProfileImage(fileBlob, 'avatar.jpg');
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Réponse possible:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"message": "Image mise à jour",
|
|
104
|
+
"user_infos": {
|
|
105
|
+
"name": "John Doe",
|
|
106
|
+
"image_url": "https://cdn.example.com/avatar.jpg"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Sécurité
|
|
112
|
+
|
|
113
|
+
- Ces helpers utilisent la session courante et le contexte device.
|
|
114
|
+
- Les changements de profil restent côté IAM.
|
|
115
|
+
- Si vous n’en avez pas besoin, vous pouvez les ignorer complètement.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Migration Notes
|
|
2
|
+
|
|
3
|
+
Version: `2.8.3`
|
|
4
|
+
|
|
5
|
+
## Version 2.8.2
|
|
6
|
+
|
|
7
|
+
- `@capacitor/device` est une peer dependency optionnelle.
|
|
8
|
+
- Le package chiffre les données persistées au repos.
|
|
9
|
+
- Le frontend SaaS récupère la session via `getSsoSessionSnapshot()`.
|
|
10
|
+
- `logout()` est le flux de sortie recommandé.
|
|
11
|
+
|
|
12
|
+
## Côté intégrateurs
|
|
13
|
+
|
|
14
|
+
- Gardez les anciens helpers uniquement pour compatibilité.
|
|
15
|
+
- Ne lisez plus le storage directement dans le frontend hôte.
|
|
16
|
+
- Si vous êtes sur Capacitor, fournissez un storage natif sécurisé si vous en avez un.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Update Infos
|
|
2
|
+
|
|
3
|
+
Version: `2.8.3`
|
|
4
|
+
|
|
5
|
+
Ce document décrit le flux recommandé pour la modal profil `OnboardingModal` quand un SaaS veut synchroniser les informations utilisateur avec le package `@ollaid/native-sso`.
|
|
6
|
+
|
|
7
|
+
## Objectif
|
|
8
|
+
|
|
9
|
+
- IAM reste la source de vérité.
|
|
10
|
+
- Le SaaS garde une copie locale de `user.user_infos`.
|
|
11
|
+
- Quand le modal s’ouvre, il hydrate les champs depuis IAM.
|
|
12
|
+
- Quand l’utilisateur enregistre, IAM est mis à jour en premier.
|
|
13
|
+
- Après succès IAM, le SaaS est mis à jour immédiatement.
|
|
14
|
+
|
|
15
|
+
## Flux recommandé
|
|
16
|
+
|
|
17
|
+
### 1. Ouverture du modal
|
|
18
|
+
|
|
19
|
+
Quand le SaaS ouvre la modal profil:
|
|
20
|
+
|
|
21
|
+
- afficher `OnboardingModal`
|
|
22
|
+
- passer le `user` courant
|
|
23
|
+
- utiliser `variant="edit"` si l’utilisateur modifie son profil depuis le SaaS
|
|
24
|
+
- garder `profileHydrating={true}` tant que les données ne sont pas encore chargées
|
|
25
|
+
|
|
26
|
+
Si vous n’utilisez pas `NativeSSOPage`, la couche SaaS doit d’abord hydrater le profil depuis IAM, puis ouvrir la modal avec les données reçues.
|
|
27
|
+
|
|
28
|
+
Pendant cette phase, la modal doit afficher un spinner de chargement.
|
|
29
|
+
|
|
30
|
+
### 2. Hydratation depuis IAM
|
|
31
|
+
|
|
32
|
+
Le package récupère les infos depuis IAM via son service profil:
|
|
33
|
+
|
|
34
|
+
- `profileService.getProfile()`
|
|
35
|
+
- endpoint IAM: `GET /backoffice/profile`
|
|
36
|
+
|
|
37
|
+
Dans le flux autonome `NativeSSOPage`, cette hydratation est déjà gérée par le package.
|
|
38
|
+
|
|
39
|
+
Les données récupérées doivent remplir les inputs du formulaire:
|
|
40
|
+
|
|
41
|
+
- `name`
|
|
42
|
+
- `image_url`
|
|
43
|
+
- `ccphone`
|
|
44
|
+
- `phone`
|
|
45
|
+
- `email`
|
|
46
|
+
- `address`
|
|
47
|
+
- `town`
|
|
48
|
+
- `country`
|
|
49
|
+
|
|
50
|
+
Si IAM renvoie une donnée plus fraîche que celle du SaaS, la valeur IAM gagne.
|
|
51
|
+
|
|
52
|
+
### 3. Mise à jour depuis la modal
|
|
53
|
+
|
|
54
|
+
Quand l’utilisateur clique sur `Enregistrer`:
|
|
55
|
+
|
|
56
|
+
- le package envoie l’update vers IAM via `profileService.updateProfile()`
|
|
57
|
+
- endpoint IAM: `PUT /backoffice/profile`
|
|
58
|
+
- IAM valide, normalise et renvoie la version finale du profil
|
|
59
|
+
|
|
60
|
+
IAM reste prioritaire sur la validation et la normalisation des champs.
|
|
61
|
+
|
|
62
|
+
### 4. Synchronisation immédiate vers le SaaS
|
|
63
|
+
|
|
64
|
+
Après succès IAM:
|
|
65
|
+
|
|
66
|
+
- le package appelle `onComplete`
|
|
67
|
+
- `onComplete` contient les données finales sous forme `user_infos`
|
|
68
|
+
- le SaaS doit persister immédiatement ces données dans son propre utilisateur
|
|
69
|
+
|
|
70
|
+
Le SaaS peut:
|
|
71
|
+
|
|
72
|
+
- mettre à jour son cache local
|
|
73
|
+
- appeler son backend pour enregistrer `user.user_infos`
|
|
74
|
+
- recharger la session utilisateur si besoin
|
|
75
|
+
|
|
76
|
+
## Règle d’or
|
|
77
|
+
|
|
78
|
+
La hiérarchie correcte est:
|
|
79
|
+
|
|
80
|
+
1. IAM
|
|
81
|
+
2. package SSO
|
|
82
|
+
3. SaaS
|
|
83
|
+
|
|
84
|
+
Donc:
|
|
85
|
+
|
|
86
|
+
- lecture depuis IAM
|
|
87
|
+
- écriture vers IAM
|
|
88
|
+
- réplication immédiate vers le SaaS
|
|
89
|
+
|
|
90
|
+
## Exemple d’intégration SaaS
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { useState } from 'react';
|
|
94
|
+
import { OnboardingModal } from '@ollaid/native-sso';
|
|
95
|
+
|
|
96
|
+
export function ProfileSettings({ user, api }: { user: NativeUser; api: any }) {
|
|
97
|
+
const [open, setOpen] = useState(false);
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<>
|
|
101
|
+
<button type="button" onClick={() => setOpen(true)}>
|
|
102
|
+
Modifier mon profil
|
|
103
|
+
</button>
|
|
104
|
+
|
|
105
|
+
<OnboardingModal
|
|
106
|
+
open={open}
|
|
107
|
+
onOpenChange={setOpen}
|
|
108
|
+
onDismiss={() => setOpen(false)}
|
|
109
|
+
user={user}
|
|
110
|
+
variant="edit"
|
|
111
|
+
profileHydrating={false}
|
|
112
|
+
onComplete={async (data) => {
|
|
113
|
+
await api.updateCurrentUser({
|
|
114
|
+
user_infos: data.user_infos,
|
|
115
|
+
});
|
|
116
|
+
setOpen(false);
|
|
117
|
+
}}
|
|
118
|
+
onSkip={() => setOpen(false)}
|
|
119
|
+
/>
|
|
120
|
+
</>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Cas du mode automatique
|
|
126
|
+
|
|
127
|
+
Quand `NativeSSOPage` gère l’auto-onboarding:
|
|
128
|
+
|
|
129
|
+
- la modal s’ouvre si le profil est incomplet
|
|
130
|
+
- le package hydrate les données depuis IAM
|
|
131
|
+
- les champs manquants sont affichés
|
|
132
|
+
- à la validation, IAM est mis à jour puis le SaaS est synchronisé
|
|
133
|
+
|
|
134
|
+
## Ce que le SaaS doit retenir
|
|
135
|
+
|
|
136
|
+
- N’inversez pas la priorité des sources.
|
|
137
|
+
- Ne traitez pas le SaaS comme source de vérité du profil.
|
|
138
|
+
- Ne contournez pas `onComplete`.
|
|
139
|
+
- Si le SaaS modifie `user.user_infos` hors modal, il doit garder la même logique de miroir vers IAM au prochain refresh.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Password Magic Link Flow
|
|
2
|
+
|
|
3
|
+
Version: `2.8.3`
|
|
4
|
+
|
|
5
|
+
Ce document décrit le flux recommandé pour ouvrir le modal de changement de mot de passe sans faire d'appel direct du navigateur vers IAM.
|
|
6
|
+
|
|
7
|
+
## Objectif
|
|
8
|
+
|
|
9
|
+
- Le frontend SaaS ouvre la modal mot de passe.
|
|
10
|
+
- Le package `@ollaid/native-sso` appelle le backend SaaS.
|
|
11
|
+
- Le backend SaaS appelle IAM en server-to-server sur `POST /api/backoffice/auth/password-link`.
|
|
12
|
+
- IAM renvoie un `magic_token` ou une URL de redirection.
|
|
13
|
+
- Le backend SaaS renvoie au package une URL de redirection prête à ouvrir.
|
|
14
|
+
- Le navigateur est redirigé vers IAM.
|
|
15
|
+
|
|
16
|
+
## Pourquoi ce flux
|
|
17
|
+
|
|
18
|
+
- Il évite le CORS entre navigateur et IAM pour cette action.
|
|
19
|
+
- Il centralise l'authentification et la journalisation côté SaaS.
|
|
20
|
+
- Il laisse IAM gérer la génération du lien magique.
|
|
21
|
+
|
|
22
|
+
## Contrat attendu côté SaaS
|
|
23
|
+
|
|
24
|
+
Endpoint conseillé:
|
|
25
|
+
|
|
26
|
+
```http
|
|
27
|
+
POST /api/native/password-link
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Headers envoyés par le package:
|
|
31
|
+
|
|
32
|
+
- `Authorization: Bearer <token>` si disponible
|
|
33
|
+
- `X-App-Access-Token-Ref` si présent dans le storage
|
|
34
|
+
- `X-IAM-Config-Prefix` si le package est configuré avec un préfixe multi-tenant
|
|
35
|
+
- `X-Device-*` pour le contexte appareil
|
|
36
|
+
|
|
37
|
+
Body:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Réponse recommandée:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"success": true,
|
|
48
|
+
"redirect_url": "https://identityam.ollaid.com/auth/auto-connect?magic_token=xxx",
|
|
49
|
+
"magic_token": "xxx",
|
|
50
|
+
"expires_at": "2026-05-18T12:00:00+00:00"
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Le backend SaaS peut aussi renvoyer `sso_url` pour compatibilité, mais `redirect_url` est la forme à privilégier.
|
|
55
|
+
|
|
56
|
+
## Contrat backend SaaS -> IAM
|
|
57
|
+
|
|
58
|
+
Le backend SaaS doit appeler IAM en interne:
|
|
59
|
+
|
|
60
|
+
```http
|
|
61
|
+
POST https://identityam.ollaid.com/api/backoffice/auth/password-link
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
IAM répond avec:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"success": true,
|
|
69
|
+
"magic_token": "xxx",
|
|
70
|
+
"sso_url": "https://identityam.ollaid.com/auth/auto-connect?magic_token=xxx",
|
|
71
|
+
"expires_at": "2026-05-18T12:00:00+00:00"
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Le backend SaaS doit ensuite:
|
|
76
|
+
|
|
77
|
+
1. valider l'utilisateur courant
|
|
78
|
+
2. relayer la requête vers IAM
|
|
79
|
+
3. retourner au package une URL de redirection prête à ouvrir
|
|
80
|
+
|
|
81
|
+
## Intégration package
|
|
82
|
+
|
|
83
|
+
Le composant `PasswordRedirectModal` du package utilise `saasApiUrl`.
|
|
84
|
+
`iamApiUrl` reste supporté temporairement pour compatibilité ascendante, mais il ne doit plus être utilisé pour ce flux.
|